threadlocal为什么这么设计?

threadlocal是比较特殊的存在,真不用也可以,但是用了会方便很多。threadlocal的基本属性不在本文讨论范围内。

 

我头脑风暴了下,threadlocal为什么是现在这么设计的。根据threadlocal的特性,如果自己实现,大部分人第一反应应该是这个方案(图片出处见水印,侵删):

 

threadlocal类里有一个map,key为threadId,value为每个线程各自的值,这个方案有两个问题:

(1)多线程共享map,则要求map线程安全。这个问题可用ConcurrentHashMap解决。但是虽然ConcurrentHashMap锁颗粒度很小,但终有锁等待的情况。存在性能损耗

(2)存在内存溢出的情况。什么情况会有内存溢出?

JVM内存情况如下图:

线程A B C的栈的栈帧中,有某类实例的引用,通过xxx.get()可以获取threadlocal变量。即引用(1)

threadlocal变量中有对ConcurrentHashMap的引用。即引用(2)

ConcurrentHashMap里key为字符串threadID,value为对具体实例的引用。即引用(3)

这就会出现一种情况,虽然线程A,B都已经将栈帧弹出,不继续使用threadlocal xxx,但是因为线程C还在使用。

甚至之后来线程D,只要仍然有一个线程在使用。即引用(1)仍存,某类就不会被回收,引用(2)仍存。

ConcurrentHashMap就不会被回收,引用(3)就会一直存在,一个都不少。则实际的值xxx-A-value和xxx-B-value一直不会被回收。

造成的结果是,线程A B已经不再使用该threadlocal,但是为之创建的区域却一直无法回收。随着线程不断增加,而旧线程相关的空间又不回收,可能ConcurrentHashMap会越来越大

发生了内存泄漏。

有一种方法可以缓解,即定时检查线程A B是否存活,已经结束的时候,可能让A,xxx-A-value的键值对删除,释放引用和空间。

但是这是在线程已经结束的情况。还可能线程虽然不再使用xxx变量,但是后续有其他工作要做,则xxx-A-value无法别回收

 

于是,threadlocal就被聪明的设计人员,改变了切面点,设计成现在这样(图出处见水印,侵删):

map不由threadlocal持有,而是由thread持有。key为threadlocal变量,value为值。每个thread所有的threadloacl都放于其中。

尤其只存放本thread自己的值,所以map没有线程安全问题,也没有锁消耗

但仍然会有内存泄漏问题,JVM内存情况如下图:

线程A B C的栈的栈帧中,有某类实例的引用,通过xxx.get()可以获取threadlocal变量。即引用(1)

线程A B C还持有对各自的ThreadLocalMap,引用无画,和线程共存亡

ThreadLocalMap中的key,为对threadlocal xxx的引用。即引用(2)

ThreadLocalMap中的value为对具体实例的引用。即引用(3)

实际引用(2)为弱引用。我们先讨论下引用(2)为强引用的情况:

如果线程A B C都使用完了该threadlocal,尽管从栈帧中被弹出,引用(1)全部断开。但是ThreadLocalMap和线程共存亡,只有不是所有相关线程都结束,强引用的引用(2)会一直存在,则该类一直不会被回收,具体实例如xxx-A-value等也不会被回收,如此下去,造成内存泄漏。但是比我们第一反应的方案好在,线程结束了,对应的实例xxx-A-value,就能被回收,不想之前的方案,要等所有线程都结束。所以引用(2)采用弱引用

 

引用(2)采用弱引用了,就真的不会发生内存溢出了吗?

线程A B C都使用完了threadlocal,都把栈帧弹出来,引用(1)都断了,引用(2)为弱引用,某类有可能能被回收掉。但是只要线程在,ThreadLocalMap就在,引用(3)就在。当线程只是不再使用该threadloacl,继续进行后续的处理,则不会再被用到的实例无法释放,内存溢出了

 

怎么办?

所以ThreadLocal有这样的机制,在一些操作点会进行检查,将threadlocal xxx变量已经被回收,变为Null的<xxx,value>的记录,给删掉。即使所有的线程还存活,引用(3)都切断了,不被使用的xxx-A-value等实例就能被回收了,不会发生内存泄漏

 

ThreadLacalMap的key采用了弱引用,value能否也采用弱引用,来避免全部扫描剔除已经不需要的键值对?

答案是不行的。因为threadlocal变量与实例与一遍的变量与实例不同,并没有建立如何的引用。他们的映射关系仅由ThreadLocalMap维系。如果连引用(3)都改成弱引用,可能变量还需要使用,对应的具体实例已经被释放掉了

 

正是由于之上那些角度的考量,threadlocal才会被设计成这样,不得不说jdk大神们的精妙与天才

 

posted @ 2018-05-30 15:12  架构之美,智慧之光  阅读(603)  评论(0编辑  收藏  举报