ThreadLocal提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同。ThreadLocal相当于提供了一种线程隔离,将变量与线程相绑定。相比于synchronized的做法是用空间来换时间。

图1 ThreadLocal类定义
ThreadLocal通过threadLocalHashCode来标识每一个ThreadLocal的唯一性。threadLocalHashCode通过CAS操作进行更新,每次hash操作的增量为 0x61c88647。

图2 内部类ThreadLocalMap
ThreadLocal有一个静态内部类ThreadLocalMap,ThreadLocalMap又包含了一个Entry数组,Entry本身是一个弱引用,它的key是指向ThreadLocal的弱引用,弱引用便于GC回收,Entry具备了保存key、value键值对的能力。
ThreadLocal每次hash加0x61c88647,二进制数值是
01100001110010001000011001000111。Entry[]数组长度保持为2^n个,那么table.length – 1即2^n-1的值二进制数表明如下:

图3 Integer型2^n-1的二进制表明如上
Entry数组定位是通过key.threadLocalHashCode & (table.length – 1)计算来的(如图4),即threadLocalHashCode&2^n-1,threadLocalHashCode值设计的比较巧妙,通过&运算可以减少冲突率,提升查询效率。

图4 ThreadLocalMap构造函数
一、ThreadLocal的set方法分析

图5 set方法
通过Thread t = Thread.currentThread();获取当前的线程引用,并传给getMap(Thread)方法,我们看下getMap方法,

图6 getMap方法
可以看到getMap(Thread)方法直接返回Thread实例的成员变量threadLocals。它的定义在Thread内部,访问级别为package级别。
到了这里,我们可以看出,每个Thread里面都有一个
ThreadLocal.ThreadLocalMap成员变量,也就是说每个线程通过
ThreadLocal.ThreadLocalMap与ThreadLocal相绑定,这样可以确保每个线程访问到的thread local variable都是本线程的。
继续分析set方法,获得ThreadLocalMap实例,如果不为空则调用ThreadLocalMap的set方法向当前线程的threadlocals添加新的threadLocal;否则调用createMap方法(如图7),发现createMap调用了ThreadLocalMap的构造函数,直接new了一个新的ThreadLocalMap实例赋值给当前线程的threadLocals,即初始化当前线程的第一个threadLocal变量。

图7 createMap方法
至此set方法分析完毕,发现set方法设置的value值最终直接绑定到了Thead类的threadlocals成员变量上,即value直接与线程进行绑定了,提供了线程隔离,达到线程安全。
二、ThreadLocal的get方法分析

图8 get方法
通过Thread.currentThread()方法获取了当前的线程引用,并传给了getMap(Thread)方法获取一个ThreadLocalMap的实例,即当前线程Thread的成员变量threadLocals。再通过ThreadLocalMap实例的getEntry(ThreadLocal)方法获取当前ThreadLocal存储的value。如果没有值,调用setInitialValue方法初始化值,默认的initialValue() 返回null值(如图10)。

图9 设置初始化值

图10 初始化值默认值null
三、ThreadLocal的remove方法分析

图11 remove方法
通过ThreadLocalMap m = getMap(Thread.currentThread());获取当前线程的ThreadLocalMap 类型的成员变量threadLocals值,然后调用ThreadLocalMap的remove方法,从Entry数组中,删除对应的ThreadLocal数据,释放entry资源。
四、ThreadLocal内存泄漏分析

图12 ThreadLocal对象的引用链
当Thread和ThreadLocal发生绑定之后,关键对象引用链如图12所示,与上述我们源码分析是一致的。当我们把ThreadLocal Ref显示地指定为null时,引用链就变成图13所示。

图13 ThreadLocal中Ref对象为null时引用链
由于Entry的key类型ThreadLocal采取了弱引用类型,当ThreadLocal被显示地指定为null之后,就会执行GC操作,此时堆内存中ThreadLocal会被回收,同时ThreadLocalMap中Entry.key也成为了了null,但是value不是弱引用并且不为null的,故不会被释放,除非当前线程已经结束了生命周期,Thread引用被垃圾回收器回收。否则会造成内存泄漏。但是只要在ThreadLocal使用恰当,在使用完之后调用remove方法删除Entry对象,就不会出现这个问题了。