整体结构
从上图中看出,在++每个Thread类中,都有一个ThreadLocalMap的成员变量++,该变量包含了一个Entry数组,该数组真正保存了ThreadLocal类set的数据。
Entry是由threadLocal和value组成,其中threadLocal对象是弱引用,在GC的时候,会被自动回收。而value就是ThreadLocal类set的数据。
下面用一张图总结一下引用关系:
上图中除了Entry的key对ThreadLocal对象是弱引用,其他的引用都是强引用。
Hash冲突
和HashMap的最大的不同在于,ThreadLocalMap结构非常简单,没有next引用,也就是说ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是==采用线性探测==的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
弱引用
Entry的key,传入的是ThreadLocal对象,使用了WeakReference对象,即被设计成了弱引用。是为解决内存泄露问题的。(可以将方法运行结束后无用的threadlocal对象回收,不然的话无法回收,造成内存泄漏)。
使用弱引用,就可以使ThreadLocal对象在方法执行完毕后顺利被回收且Entry的key引用指向为null。
ThreadLocal一个线程只能存放一个变量吗?想存多个怎么搞? - 知乎 (zhihu.com)
内存泄漏
但是即使设计为弱引用,还是会有内存泄漏的问题(假如ThreadLocalMap中存在很多key为null的Entry,但后面的程序,一直都没有调用过有效的ThreadLocal的get、set或remove方法。
那么,Entry的value值一直都没被清空。长此以往,造成内存泄露),因此使用完threadlocal变量后,一定要通过调用ThreadLocal对象的remove方法。
但是弱引用也不是万事大吉了。当我们为threadLocal变量赋值,实际上就是当前的Entry(threadLocal实例为key,值为value)往这个threadLocalMap中存放。Entry中的key是弱引用,当threadLocal外部强引用被置为null(tl=null),那么系统 GC 的时候,根据可达性分析,这个threadLocal实例就没有任何一条链路能够引用到它,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话(使用线程池的情况下),这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
但在实际使用中我们有时候会用线程池去维护我们的线程,比如在Executors. newFixedThreadPool()时创建线程的时候,为了复用线程是不会结束的,所以threadLocal内存泄漏就值得我们小心。