Java之ThreadLocal

第一次看到ThreadLocal的介绍一脸懵逼,到底为啥使用ThreadLocal,ThreadLocal和成员变量有啥不同呢...今天趁着有空来彻底搞清楚ThreadLocal这个玩意儿~

《Java并发并发编程实战》里面提到ThreadLocal是一种实现维持线程封闭性的方案。ThreadLocal提供了set和get的方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回有当前执行线程在调用set时设置的最新值。

这么一看。。。。这说的都是啥!!!感觉依旧是云里雾里,先看下ThreadLocal的源码理解下了~

一、ThreadLocal中的方法

首先看下ThreadLocal中的方法

1、initialValue

源码中的第一个方法

注释写的很清楚

注释写的很清楚了,简单翻译下:线程第一次调用get或者set时会调用,如果程序员希望返回null以外的值,需要对这个方法进行重载。

2、withInitial

JDK1.8之后的构造方式,这个也不多说了。

3、get

ThreadLocalMap,后面再说。

getMap:直接返回当前线程的threadLocals变量。看Thread源码我们可以看到每一个Thread都有一个名字做threadLocals、类型为ThreadLocalMap的变量,这个方法就是返回当前线程的threadLocals变量。

如果getmap的返回值不为null,则返回key为 当前ThreadLocal 的value值,如果为空的话,继续调用setInitialValue方法。其实源码看到这,可以猜测,ThreadLocalMap存储的是key为ThreadLocal,value为泛型的map。

3、setInitialValue


设置ThreadLocalMap里面key为当前ThreadLocal的value值,返回Value。

4、set

设置key为ThreadLocal里value的值。

5、remove

二、ThreadLocalMap

ThreadLocal中的方法看的差不多了,ThreadLocal里面有一个非常重要的类型ThreadLocalMap,感觉是ThreadLocal的核心,接下来看下ThreadLocalMap是怎么回事。

划重点:ThreadLocalMap的key是弱引用,这意味着每次gc都会将key回收,value也就无法访问,这有可能导致内存泄漏。

1、Entry

继承WeakReference,包含两个元素:threadLocal和Object

2、构造器

ThreadLocalMap有两个构造器

(1)首先初始化Entry,然后根据firstKey计算hash值,放入Entry中。

(2)传入一个父ThreadLocalMap:patentMap,将patentMap Key非空的Entry设置到当前的threadLocalMap中。

3、set

过程:

(1)首先根据Key值计算出index的值,对应至数组Entry的索引E,并且从E开始进行遍历

(2)如果查找到对应的Key的Entry,替换Value。

(3)如果找到无效状态的Entry,则替换无效状态的Entry(见 4、replaceStaleEntry),并返回。

(4)以上都没有命中,新建一个Entry用于设置新的Key和Value,整个map的size加1。之后进行一个擦除过程(见 5、cleanSomeSlots),如果成功擦除,证明数组没有变大,,如果没有擦除成功,需要判断测试的size是否大于了扩容临界值(size的三分之二),如果大于,重新进行hash。

4、replaceStaleEntry

代码有点长,就不放了。主要功能就是替换stale状态的Entry。stale状态会在 5、cleanSomeSlots 中介绍。

5、cleanSomeSlots 

Stale状态:前文说ThreadLocalMap的key是弱引用,gc时会被回收,这时对应的Entry就是stale状态。这个时候就会对Entry进行擦除,防止内存泄漏(PS:初看到key是弱引用还是有点震惊的,原来每次set的时候都会擦除一波。。。)

第656行是判断key是否被gc的,如果gc了就执行expungeStaleEntry,即擦除此Entry。

三、总结

ThreadLocal的原理应该是维护了一个静态内部类ThreadLocalMap,ThreadLocalMap为每个ThreadLocal都维护了一个Entry,放入Entry数组table。

其实ThreadLocal就是相当于维护了一个map,map的key是当前Threadlocal的弱引用,value是需要存储的对象。

“首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量(弱引用),value为变量副本(即T类型的变量)。

初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。

然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。”

以上引用自

https://blog.csdn.net/Faker_Wang/article/details/81298740

四、关于ThreadLocal的OOM

ThreadLocal中ThreadLocalMap中的key使用的是弱引用。虽然在get、set等方法中会执行擦除操作,但是还是有OOM的风险。

ThreadLocal作为弱引用,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。

发现一篇博文讲的比较透彻,就不在这班门弄斧了,详情https://blog.csdn.net/xlgen157387/article/details/78298840

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容