第一次看到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