一、介绍
ThreadLocal,顾名思义,线程本地变量,只属于当前线程,其他线程无法获取这个变量,是隔离的。不同线程虽然有相同的变量名,但是实际地址各不相同。
每个线程都有自己的一个副本。
每个Thread都有一个与之关联的ThreadLocalMap,map中保存了一个键值对Entry,其中键值为ThreadLocal,值是通过Threadlocal.set(value)设置的值,不过Entry指向ThrealdLocal的引用为软引用。当方法中指向ThreadLocal的变量丢失后,entry中维护了一个弱引用指向ThreadLocal对象,当经过一次垃圾回收后,弱引用也被回收,就没有办法获取到value,但是value内存不能回收,产生内存泄漏。
二、源码分析
//获取值
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//从当前线程获取的成员变量是否为空
if (map != null) {
//获取map的键值对
ThreadLocalMap.Entry e = map.getEntry(this);
//entry不为空则取entry的value
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//
return setInitialValue();
}
//返回当前线程的ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//为当前线程创建ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//设置初始值,
private T setInitialValue() {
//设置初始值为null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return 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);
}
1)每个线程都有一个ThreadLocalMap对象;
2)threadLocal.get()方法其实是获取当前线程的threadlocalmap对象,然后以threadlocal为key,通过开放选址法获取map中存储的value;
3)ThreadLocalMap是底层是一个存储Entry的数组,存储threadlocal对象时,先计算threadlocal的hash值,然后查找在Entry数组中的位置,Entry对象保存键值对,对key的保存通过弱引用进行保存,对valu使用强引用保存。Entry类持有ThreadLocal的弱引用,持有ThreadLocal中保存变量的强引用。
4)Threadlocal通过开放寻址法在ThreadLocalMap中查找键值对,FastThreadLocal 获取其中保证的变量值时,使用内部的index变量便可定位对应变量值
5)如果不在线程中进行remove(),当进行垃圾回收时,Entry中对ThreadLocal的弱引用将会导致ThreadLocal被回收,但是Entry中的值是强引用不会被回收,产生内存泄漏。
三、子线程如何获取父线程的ThreadLocal值
可以使用线程的InheritablThreadLocalMap,这个在创建线程对象的时候会复制到子线程中。
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
InheritableThreadLocal也是一个ThreadLocal对象,不过它是存储在Thread的inheritableThreadLocale变量中,在创建对象的时候,会从父类中拷贝一份副本。
四、ThreadLocal的内存泄漏
因为ThreadLocale保存在ThreadLocalMap中,但是他的键值对中key是一个弱引用,在进行垃圾回收时,就会被jvm回收,但是value是一个强引用无法被回收,导致内存泄漏。
当将threadlocal的引用设为null后,ThreadLocal就只有entry的key进行关联,key是一个弱引用,垃圾回收时,就会被干掉。
解决方案:1、在ThreadLocal 设为null前,将key为空的键值对情况。
2、将ThreadLoal设为static
3、非必要不要放大对象在ThreadLocal中
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
五、使用场景
1、数据库连接、处理数据库事务
每个线程都有一个连接对象副本,不用担心造成线程安全问题,进行回滚或者其他操作,都不会受到干扰。
2、数据跨层传递
通过静态方法传递参数,避免参数传递的麻烦。
3、spring中使用ThreadLocal解决线程安全问题
equestContextHolder、TransactionSynchronizationManager、LocaleContextHolde等可以直接获取ThreadLocal对象。