随笔篇-ThreadLocal原理分析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了随笔篇-ThreadLocal原理分析相关的知识,希望对你有一定的参考价值。

参考技术A

ThreadLocal 存取值都是借助 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 工作原理部分源码分析

008-ThreadLocal原理分析

多线程爬坑之路-ThreadLocal源码及原理的深入分析

分析Threadlocal内部实现原理,并解决Threadlocal的ThreadLocalMap的hash冲突与内存泄露

源码分析:InheriableThreadLocal传递数据的原理和ThreadLocal导致的内存泄露原因