1. 存取值分析
1.0 前言
ThreadLocal存取值都是借助ThreadLocalMap对象去进行存取值,而ThreadLocalMap类是定义在ThreadLocal类中的一个静态内部类

在再存取值时都是获取当前线程的ThreadLocalMap实例,即每个线程都会创建一个ThreadLocalMap类型对象,如下:

该类型为实例变量,也就是说每个线程对象都会拥有一个
threadLocalMap对象
而ThreadLocalMap本质就是一个就是Map类型,在其内部也维护了一个Entry类型对象,如下:

这样就与HashMap类似,存储的也是key-value类型数据
1.1 存值
从上文的表述中,存储值都是通过ThreadLocalMap进行操作,根据源码可知,主要分为三个步骤:
- 获取当前线程
- 获取
ThreadLocalMap类型对象 - 判断是否为空,为空创建,否则存值
关于每个步骤详细解释如下:
获取当前线程

根据线程实例获取ThreadLocalMap类型对象

而获取ThreadLocalMap对象则需要传入一个线程对象,查看getMap源码可知,其实就是返回了线程对象的ThreadLocalMap实例对象

而通过之前的1.0章节我们知道,线程对象里面的threadLocals对象并没有实例化,只是给了null值
因此当第一次获取
threadLocals这个实例时,最后的结果肯定为空
实例对象为空则创建ThreadLocalMap类型对象
从其源码可知,当实例对象为空的时候,就回去创建ThreadLocalMap,创建该类型对象需要传入当前线程对象,以及要存储的值,如下:

查看createMap源码可知,就是new了一个ThreadLocalMap对象
- key值为
this也就是当前的ThreadLocal对象 - value 为具体要存入
ThreadLocal的值
最后将创建好的对象赋值给 线程对象里面的ThreadLocalMap实例,源码如下:

这样在一个线程中始终只有一个
ThreadLocalMap类型对象,但是这个类型中可以存放多个ThreadLocal对象值
如果 map不为空,则将值存入ThreadLocalMap中,key为当前的ThreadLocal对象

上述步骤其流程图如下:

可能这里图看的并不清晰,可以查看: 高清大图
1.2 取值
ThreadLocal取值原理与存值原理相似,都是几个固定步骤,如下:
- 获取当前线程对象
- 根据线程对象获取
ThreadLocalMap实例 - 如果
ThreadLocalMap实例不为空,则获取值 - 如果
ThreadLocalMap实例为空,则调用setInitValue方法
关于这几个步骤详细解释如下
获取当前线程对象
根据线程对象获取ThreadLocalMap实例

这两个步骤在上文中进行了详细阐述,这里就不再过多赘述
如果ThreadLocalMap实例不为空,则获取值
这里取值并不是直接从Map中获取value,而是根据this(当前ThreadLocal)对象取获取Entry实体
如果Entry实体不为空,则获取Entry的value,如下:

如果ThreadLocalMap实例为空,则调用setInitValue方法
从源码可知,当ThreadLocalMap实例为空时,则调用setInitValue方法,而该方法
则又是先调用了initialValue方法获取value值,也就是存储要初始化存储的值
接下来就又是存值的几个步骤,源码如下:

通过查看initiaValue方法可只,该方法为返回null

但是如果子类重写了该方法,返回并不是null,那么map中存储的就不是null值
关于上述步骤流程图如下:

流程图不太清晰,如果想要查看清晰,可以通过该网址查看:高清大图
1.3 总结
从上文的表述中知道,往ThreadLocal中存值一共有两种方式
- 直接创建
ThreadLocal对象,并且通过set方式进行存值 - 或者子类继承
ThreadLocal,然后重写initialValue方法
当然取值就只通过get方法了,一个线程从始至终就只有一个ThreadLocalMap对象
而一个ThreadLocalMap可以存储多个ThreadLocal,即一个线程可以有多个ThreadLocal对象
当然一个ThreadLocal对象只能存值一个实例对象,并不能存储多个实例,原因在上文中也进行了阐述
2. 内存泄漏
2.0 前言
当一个线程启动,并且给创建了一个ThreadLocal对象,在堆栈中内存简图如下:

当发生内存泄漏时,有可能时ThreadLocalMap里面的key发生泄漏,也有可能是Map发生value发生泄漏,因此接下来来详细分析一下泄漏原因
2.1 分析
通过源码可知,在ThreadLocalMap中,可以发现Entry继承了WeakReference,且在其构造方法中,将key通过调用父类方法进行了弱引用处理,如下:

弱引用是指这个如果对象的引用是一个弱引用,那么当下一次发生
GC的时候一定会被回收掉
从源码知道,也就是ThreadLocalMap中的ThreadLocal对象一旦回收掉,也就是说key就为null了。
但是value确实一个强引用,即ThreadLocalMap中存在 key为null的value,如下:

如果当前线程对象,执行一个耗时操作,长时间不被销毁,也就意味着ThreadLocalMap中key为value的数据,永远没法被GC,因此可能发生内存泄漏(OOM)
2.2 解决
JDK在设计时已经考虑到这些问题,例如当调用set(),remove(),rehash()方法会手动清理key为null的数据,例如查看set()方法调用链可知,最后调用了resize()方法,如下:

在resize方法中,会主动的将key为null的数据也制空,以便防止内存泄漏,如图:

因此如果当ThreadLocal发生内存泄漏了,那么肯定是存在了key为null的数据,所以在阿里开发规范中, 提到了如果ThreadLocal使用完成,那么应该手动调用一下remove

思考:不是已经做了优化了么,怎么还会存在key为null的数据呢?
其实当一个
ThreadLocal不再被被使用,那么resize方法就不会调用,如果线程不停止,那么就会发生OOM有可能