1、ThreadLocal的原理

threadlocal.png

每个Thread内部维护着一个ThreadLocalMap,它是一个Map。这个映射表的Key是一个弱引用,其实就是ThreadLocal本身,Value是真正存的线程变量Object。

也就是说ThreadLocal本身并不真正存储线程的变量值,它只是一个工具,用来维护Thread内部的Map,帮助存和取。注意上图的虚线,它代表一个弱引用类型,而弱引用的生命周期只能存活到下次GC前。

2、ThreadLocal的源码探寻


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);
}

需要注意两点:
(1)ThreadLocal里面包含ThreadLocalMap
(2)ThreadLocal把自己放在了ThreadLocalMap里面,然后将ThreadLocalMap挂靠到Thread下面

3、ThreadLocal为什么会内存泄漏?

ThreadLocal在ThreadLocalMap中是以一个弱引用身份被Entry中的Key引用的,因此如果ThreadLocal没有外部强引用来引用它,那么ThreadLocal会在下次JVM垃圾收集时被回收。这个时候就会出现Entry中Key已经被回收,出现一个null Key的情况,外部读取ThreadLocalMap中的元素是无法通过null Key来找到Value的。因此如果当前线程的生命周期很长,一直存在,那么其内部的ThreadLocalMap对象也一直生存下来,这些null key就存在一条强引用链的关系一直存在:Thread --> ThreadLocalMap-->Entry-->Value,这条强引用链会导致Entry不会回收,Value也不会回收,但Entry中的Key却已经被回收的情况,造成内存泄漏。
但是JVM团队已经考虑到这样的情况,并做了一些措施来保证ThreadLocal尽量不会内存泄漏:在ThreadLocal的get()、set()、remove()方法调用的时候会清除掉线程ThreadLocalMap中所有Entry中Key为null的Value,并将整个Entry设置为null,利于下次内存回收。

如下面的代码:


ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();

这个里面包括两个东西:

(1)LOCAL,我们不用管,JVM会自己回收的。
(2)ErrorContext是放在线程里面的,它跟LOCAL 没有直接的内外关系,LOCAL 跟它不是容器和存放物的关系,而是对等的Key-Value关系。LOCAL 的回收和ErrorContext的回收是相互独立的。当LOCAL被回收之后,Key为null,但是Value则出现内存泄漏。