大致理解
顾名思义,ThreadLocal指的就是Thread的本地文件,即每一个线程专属的本地变量。
详细定义如下:
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被
private static
修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
因此,当一个实例需要在多个线程中使用,且每个线程都希望该实例不会被其他线程说使用,这种情况下把实例放入ThreadLocal类中将会十分方便。常用场景有数据库链接和Session管理等。
深入分析
说完了基本意义和使用场景后,接下来需要分析ThreadLocal类的内部构造,明白其是如何实现的。
观察ThreadLocal类的内部结构后发现其内部包含了ThreadLocalMap的内部类,该map中的key
为ThreadLocalMap的弱引用,value
储存了所需的实例副本,其中ThreadLocalMap由每个线程自行维护。
static class Entry extends WeakReference<ThreadLocal> {
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
初始化实例
当线程中ThreadLocalMap为空时,线程便会初始化map,如果存在map就会将实例放入map中。
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;
}
其中ThreadLocalMap是每个线程单独维护的,都是取自于Thread
类中的threadLocals变量。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
需要注意的是,如果没有重写initialValue
方法,实例默认为null
。
protected T initialValue() {
return null;
}
获取实例
get
方法首先获取了当前线程对象,然后通过getMap
方法获取了当前线程中的ThreadLocalMap,接着以ThreadLocal为key
寻找到对应的实例,最后判断实例是否为空,如果不为空直接返回,如果为空则初始化后再返回。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
设置实例
设置实例与获取实例相似,也是获取当前线程中的ThreadLocalMap,然后以ThreadLocal为key
设置其value
。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
内存泄漏
分析完了ThreadLocal相关源码后,将其内存模型构建出来。
从内从模型可以看到ThreadLocalMap使用ThreadLocal的弱引用作为key
,当ThreadLocal没有外部强引用时这会被GC
回收,不过Entry中的 value
因为被 ThreadLocalMap所引用,因此就会出现key
为null
的Entry。这种情况下将导致value
无法引用并不能回收,造成内存泄漏。其实,ThreadLocalMap设计时已经考虑到这种情况,每当调用ThreadLocal的get()
,set()
,remove()
的时候都会清除线程ThreadLocalMap里所有key
为null
的value
。
不过错误的代码编写难免会导致内存泄漏的发生,常见的有:
- 使用
static
的ThreadLocal,延长了ThreadLocal的生命周期,可能导致的内存泄漏(参考ThreadLocal 内存泄露的实例分析)。 - 分配使用了ThreadLocal又不再调用
get()
,set()
,remove()
方法,那么就会导致内存泄漏。
因此,每次使用完ThreadLocal,都需要调用其remove()
方法清除数据,避免内存泄漏。
参考文献
深入分析 ThreadLocal 内存泄漏问题
Java并发编程:深入剖析ThreadLocal
Java进阶(七)正确理解Thread Local的原理与适用场景