随笔篇-ThreadLocal原理分析
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了随笔篇-ThreadLocal原理分析相关的知识,希望对你有一定的参考价值。
参考技术AThreadLocal 存取值都是借助 ThreadLocalMap 对象去进行存取值,而 ThreadLocalMap 类是定义在 ThreadLocal 类中的一个静态内部类
在再存取值时都是获取当前线程的 ThreadLocalMap 实例,即每个线程都会创建一个 ThreadLocalMap 类型对象,如下:
而 ThreadLocalMap 本质就是一个就是Map类型,在其内部也维护了一个Entry类型对象,如下:
这样就与 HashMap 类似,存储的也是key-value类型数据
从上文的表述中,存储值都是通过 ThreadLocalMap 进行操作,根据源码可知,主要分为三个步骤:
关于每个步骤详细解释如下:
获取当前线程
根据线程实例获取 ThreadLocalMap 类型对象
而获取 ThreadLocalMap 对象则需要传入一个线程对象,查看 getMap 源码可知,其实就是返回了线程对象的 ThreadLocalMap 实例对象
而通过之前的 1.0 章节我们知道,线程对象里面的 threadLocals 对象并没有实例化,只是给了 null 值
实例对象为空则创建 ThreadLocalMap 类型对象
从其源码可知,当实例对象为空的时候,就回去创建 ThreadLocalMap ,创建该类型对象需要传入当前线程对象,以及要存储的值,如下:
查看 createMap 源码可知,就是 new 了一个 ThreadLocalMap 对象
最后将创建好的对象赋值给 线程对象里面的 ThreadLocalMap 实例,源码如下:
如果 map 不为空,则将值存入 ThreadLocalMap 中,key为当前的 ThreadLocal 对象
上述步骤其流程图如下:
ThreadLocal 取值原理与存值原理相似,都是几个固定步骤,如下:
关于这几个步骤详细解释如下
获取当前线程对象
根据线程对象获取 ThreadLocalMap 实例
如果 ThreadLocalMap 实例不为空,则获取值
这里取值并不是直接从Map中获取value,而是根据 this (当前 ThreadLocal )对象取获取 Entry 实体
如果 Entry 实体不为空,则获取Entry的 value ,如下:
如果 ThreadLocalMap 实例为空,则调用 setInitValue 方法
从源码可知,当 ThreadLocalMap 实例为空时,则调用 setInitValue 方法,而该方法
则又是先调用了 initialValue 方法获取value值,也就是存储要初始化存储的值
接下来就又是存值的几个步骤,源码如下:
通过查看 initiaValue 方法可只,该方法为返回 null
关于上述步骤流程图如下:
从上文的表述中知道,往 ThreadLocal 中存值一共有两种方式
当然取值就只通过get方法了,一个线程从始至终就只有一个 ThreadLocalMap 对象
而一个 ThreadLocalMap 可以存储多个 ThreadLocal ,即一个线程可以有多个 ThreadLocal 对象
当然一个 ThreadLocal 对象只能存值一个实例对象,并不能存储多个实例,原因在上文中也进行了阐述
当一个线程启动,并且给创建了一个 ThreadLocal 对象,在堆栈中内存简图如下:
当发生内存泄漏时,有可能时ThreadLocalMap里面的key发生泄漏,也有可能是Map发生value发生泄漏,因此接下来来详细分析一下泄漏原因
通过源码可知,在 ThreadLocalMap 中,可以发现Entry继承了 WeakReference ,且在其构造方法中,将key通过调用父类方法进行了弱引用处理,如下:
从源码知道,也就是 ThreadLocalMap 中的 ThreadLocal 对象一旦回收掉,也就是说key就为null了。
但是value确实一个强引用,即 ThreadLocalMap 中存在 key为null的value,如下:
如果当前线程对象,执行一个耗时操作,长时间不被销毁,也就意味着 ThreadLocalMap 中key为value的数据,永远没法被 GC ,因此可能发生内存泄漏(OOM)
JDK 在设计时已经考虑到这些问题,例如当调用 set() , remove() , rehash() 方法会手动清理key为null的数据,例如查看 set() 方法调用链可知,最后调用了 resize() 方法,如下:
在 resize 方法中,会主动的将key为null的数据也制空,以便防止内存泄漏,如图:
因此如果当 ThreadLocal 发生内存泄漏了,那么肯定是存在了 key 为null的数据,所以在阿里开发规范中, 提到了如果ThreadLocal使用完成,那么应该手动调用一下remove
以上是关于随笔篇-ThreadLocal原理分析的主要内容,如果未能解决你的问题,请参考以下文章
分析Threadlocal内部实现原理,并解决Threadlocal的ThreadLocalMap的hash冲突与内存泄露