ThreadLocal底层原理
- 一:什么是ThreadLocal
- 二:理解ThreadLocal中的内存泄漏问题
- 三:ThreadLocalMap中的Hash冲突处理
- 四:可以被继承的ThreadLocal——InheritableThreadLocal
一:什么是ThreadLocal
ThreadLocal是一个创建线程局部变量的类,ThreadLocal中的变量只有当前自身线程可以访问,别的线程都访问不了。
实现原理:
每一个线程都有一个对应的Thread对象,而Thread类有一个ThreadLocalMap类型变量(threadLocals)和一个内部类ThreadLocal。这个threadLocals的key就是ThreadLocal的引用,而value就是当前线程在key所对应的ThreadLocal中存储的值。当某个线程需要获取存储在自己线程Thread的ThreadLocal变量中的值时,ThreadLocal底层会获取当前线程的Thread对象中的Map集合threadLocals,然后以ThreadLocal作为key,从threadLocals中查找value值。这就是ThreadLocal实现线程独立的原理。
为什么用Entry数组而不是Entry对象?
因为在业务代码中能new好多个ThreadLocal对象,各司其职。但是在一次请求里,也就是一个线程里,ThreadLocalMap是同一个,而不是多个。不管你new几次ThreadLocal,ThreadLocalMap在一个线程里就一个,因为ThreadLocalMap的引用是在Thread里的。所以它里面的Entry数组存放的是一个线程里你new出来的多个ThreadLocal对象。
二:理解ThreadLocal中的内存泄漏问题
ThreadLocal.ThreadLocalMap是一个比较特殊的Map,它的每个Entry的key都是一个弱引用。
这样设计的好处是,如果这个变量不再被其他对象使用时,可以自动回收这个ThreadLocal对象,避免可能的内存泄露。
注意:Entry中的value,依然是强引用。
ThreadLocalMap中的key是弱引用,当不存在外部强引用的时候,就会自动被回收。但是Entry中的value依然是强引用。这个value的引用链条如下:
可以看到,只有当Thread被回收时,这个value才有被回收的机会。否则,只要线程不退出,value总是会存在一个强引用。但是,要求每个Thread都会退出,是一个极其苛刻的要求。对于线程池来说,大部分线程会一直存在在系统的整个生命周期内,那样的话,就会造成value对象出现泄漏的可能。处理的方法是,当你不需要这个ThreadLocal变量时,主动调用remove(),这样对整个系统是有好处的。
/**
* 将当前线程局部变量的值删除,目的是为了减少内存占用。主要目的是防止内存泄漏。
*/
public void remove() {
// 获取当前线程的ThreadLocalMap对象,并将其移除。
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
// 直接移除以当前ThreadLocal为key的value
m.remove(this);
}
三:ThreadLocalMap中的Hash冲突处理
ThreadLocalMap作为一个HashMap和java.util.HashMap的实现是不同的。对于java.util.HashMap使用的是链表法来处理冲突:
但是,对于ThreadLocalMap,它使用的是简单的线性探测法,如果发生了元素冲突,那么就使用下一个槽位存放:
四:可以被继承的ThreadLocal——InheritableThreadLocal
ThreadLocal真的只是当前线程可见吗?
在实际开发过程中,我们可能会遇到这么一种场景。主线程开了一个子线程,但是我们希望在子线程中可以访问主线程中的ThreadLocal对象,也就是说有些数据需要进行父子线程间的传递。
因为在子线程中,是没有threadLocal的。如果我们希望子线可以看到父线程的ThreadLocal,那么就可以使用InheritableThreadLocal。顾名思义,这就是一个支持线程间父子继承的ThreadLocal。