如何去理解ThreadLocal?

ThreadLocal是什么?ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景.

ThreadLocal特点:就是在一个线程里放一个数据,不管中间执行了什么操作。只要想获取出来的时候,调用get就可以得到保存进去的数据.

ThreadLocal内部结构图

从上面的结构图中,我们可以看到ThreadLocal的核心机制

每个Thread 内部都有一个Map。Map里面存储线程本地对象(key) 和线程的变量副本(value)。Thread 内部的Map是由 ThreadLocal为的,由ThreadLocal负责向map获取和设置线程的变量值。Thread线程内部的Map在类中描述如下:

ThreadLocal 为什么会内存泄漏我们先分析一下ThreadLocalMap

我们可以知道每个Thread 维护一个 ThreadLocalMap,这个映射表的 key 是 ThreadLocal实例本身,value 是真正需要存储的 Object,也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。仔细观察ThreadLocalMap,这个map是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。

这样,当把threadlocal变量置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收。这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref - Thread - ThreaLocalMap - Entry - value,而这块value永远不会被访问到了,所以存在着内存泄露。

其实java 开发者,也考虑到了此问题,所以在get,set的时候,调用了expungeStaleEntry方法用来清除Entry中Key为null的Value,但是这是不及时的,也不是每次都会执行的,所以一些情况下还是会发生内存泄露。只有remove方法中显式调用了expungeStaleEntry方法。

下面看下ThreadLocal 的get方法的实现:

下面继续看 map.getEntry方法

当key 为null 时,调用getEntryAfterMiss方法

当key 为null 时,调用expungeStaleEntry 方法

也许有人会好奇,有上面方法,为什么还会导致内存泄漏呢?

一般我们设置的ThreadLocal设置为static的,static 变量可以作为GCRoot的根节点,所以会一直存在初始化了ThreadLocal, 调用set ,get 而没有调用remove方法,所以会导致内存泄漏。比如get方法,只有ThreadLocalMap中没有所需要的key时,才会调用清除方法

你可能想看:
分享给朋友: