ThreadLocal是开发中非常实用的工具,比如用户个人信息等数据可以存储在ThreadLocal中,今天来解析一下源码,至于Java虚拟机层面,ThreadLocal数据的实际内存存储等待后续学习更新。
initialValue()
protected T initialValue() {
return null;
}
initValue()是一个初始化方法,开发者可以直接进行Override,想初始化什么数据都可以在这个方法中实现,该方法仅仅只是返回ThreadLocalMap中的value值。
withInitial()
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
return supplier.get();
}
}
withInitial()其实也是一个初始化方法,入参必须继承ThreadLocal指定的类,SuppliedThreadLocal是ThreadLocal的子类,暂时没发现什么特殊的点。withInitial()用法如下:
private static final ThreadLocal<Map<Object, Object>> threadLocal = ThreadLocal.withInitial(HashMap::new);
get()
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // ThreadLocalMap存储在线程中,get的时候需要从当前线程 t 拿数据
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // this是当前的ThreadLocal对象,这边就是简单的从map中,根据key取值,开发者可以创建多个ThreadLocal
if (e != null) {
// 返回数据
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果当前ThreadLocalMap还是null,则需要给当前线程初始化一下,看setInitialValue()的解析
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
get()方法首先从当前线程中拿到ThreadLocalMap,再从ThreadLocalMap中拿到开发者指定的ThreadLocal的value。这边可能会有点绕,总结起来其实就是以下三点:
ThreadLocalMap是Thread的一个属性,即这个东西跟着线程走的,取的时候自然要从当前线程拿ThreadLocalMap是一个Entrt[]数组,Entry的中是ThreadLocal和存储的数据的键值对- 创建的每个
ThreadLocal对象,都是存储在ThreadLocalMap中
set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
这边的操作与get()方法类似,都是要从当前线程中拿到ThreadLocalMap,set当前<ThreadLocal, value>。如果没有ThreadLocalMap,自然要先创建一个ThreadLocalMap,同时用当前set的<ThreadLocal, value>来进行ThreadLocalMap的初始化。
setInitialValue()
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
与set()方法类似,区别就在于,set的value是外界提供的,还是ThreadLocal自身的initialValue()方法,不过initialValue()也可以Override,根据用户的需求进行初始化。
remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
remove()方法,是将当前的ThreadLocal,从ThreadLocalMap中移除,如果是使用线程池的Thread,这个方法很重要,举个例子:
- A用户登陆系统进行操作,后台分配了个Thread-1,A的信息在Thread-1的ThreadLocal中存储着,A走完了业务流程,Thread-1带着存储A的信息的
ThreadLocal回到了线程池; - B用户登陆系统进行操作,这时候后台同样分配了Thread-1用于处理用户B的操作,这时候有可能会读取Thread-1中存储的用户A的
ThreadLocal数据。
ThreadLocal在JVM中的实现
上文说过,ThreadLocalMap是存储ThreadLocal变量的地方,ThreadLocalMap内部是一个Entry[]数组:
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 下面的代码先不管
....
}
ThreadLocalMap并不是如我们熟知的HashMap一样,直接继承了AbstractMap,而是一个全新的类,ThreadLocalMap.Entry自然也是不同的,只不过他们都是使用哈希算法计算key的index,并存储到Entry[]数组中。ThreadLocalMap.Entry的定义并不是Map.Entry<K,V>这样明显的k-v定义,ThreadLocalMap.Entry继承了WeakReference<ThreadLocal< ? >>,并且,使用WeakReference<ThreadLocal< ? >>作为ThreadLocalMap的key。为什么要用WeakReference进行一层包装呢?为了防止内存泄漏,如果线程池中的Thread一直存在,那么ThreadLocal自然也会一直存在。
什么是弱引用?弱引用被垃圾回收器扫描到就会被回收,不会产生内存泄漏,这一点很清楚,但是问题是,这样就不怕需要使用的数据,被误回收吗,还需要了解实际的JVM引用机制,这块还没搞清楚,待补充。
2022.03.26更新 发现这篇文章讲的ThreadLocal内存泄漏 非常棒,深入分析 ThreadLocal 内存泄漏问题
2022.04.16更新 ThreadLocal在MDC中的应用,MDC是非常实用的包,MDC是什么鬼?用法、源码一锅端