Java之ThreadLocal浅析

161 阅读3分钟

本文源码来自JDK 8

ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构,与当前线程相关联。一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。

1 Thread、ThreadLocal和ThreadLocalMap的关系

  • 每个Thread对象,都有自己的ThreadLocalMap实例;
  • map中的key,是经过WeakReference包装了的ThreadLocal对象,value即ThreadLocal.set(T value)传入的值。 image.png

ThreadLocal中并没有一个private T value;属性,因为它本身不存储值。

2 ThreadLocalMap

源码中有如下类注释

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. However, since reference queues are not used, stale entries are guaranteed to be removed only when the table starts running out of space.

为了帮助处理非常大且存在时间很长的使用,散列表条目使用WeakReferences作为键。但是,由于没有使用引用队列,因此仅在哈希表开始耗尽空间时,才删除失效条目。

ThreadLocalMap是ThreadLocal的嵌套类。每个Thread对象,都有自己的threadLocalMap。 image.png

2.1 主要属性

// 默认的初始容量
private static final int INITIAL_CAPACITY = 16;
// 哈希表
private Entry[] table;
// entry数量
private int size = 0;
// 阈值,取哈希表长度的2/3
private int threshold;

Entry类是WeakReference的子类,持有threadLocal的弱引用和value。 image.png 注意,这儿Entry不是链表结构,不像HashMap通过分离链表法来解决hash冲突。ThreadLocalMap中使用了线性探测法。

2.2 初始化

在线程中首次调用ThreadLocal的get()或set()方法时,会触发初始化线程的ThreadLocalMap对象。 image.png image.png image.png

2.3 线性探测解决哈希冲突

从set()方法实现,可以清楚的看到,ThreadLocalMap使用了线性探测法来解决哈希冲突。 image.png image.png

2.4 删除失效值

当Entry中threadLocal弱引用被垃圾回收器回收后,这个entry实质上已经失效,也无法访问到value。 此时会触发expungeStaleEntry方法,删除无效entry。 image.png

调用ThreadLocal.remove()时,就会调用expungeStaleEntry,移除entry项。 image.png 此外,ThreadLocal的get()、set()方法,也有可能触发expungeStaleEntry。

3 ThreadLocal

在Java1.2起,提供了ThreadLocal类,源码中有如下类注释:

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

只要线程是活的,并且ThreadLocal实例是可访问的,每个线程都持有对线程局部变量副本的隐式引用;在线程消失后,它的线程本地实例的所有副本都将被垃圾收集(除非存在对这些副本的其他引用)

3.1 提供默认值

子类可重写initialValue();当value为null时,调用get()会将initialValue()的返回值放入map。 image.png 在ThreadLocal中,子类SuppliedThreadLocal增加了Supplier<? extends T> supplier属性,提供默认值。 image.png

3.2 存取value

get()方法中,如果map或value为null,将触发初始化动作。 image.png image.png

set()方法逻辑比较简单。 image.png

4 内存泄漏

4.1 不会内存泄露的场景

通过Thread类或者Thread类的子类来创建线程,任务执行完毕,线程退出;threadLocalMa也就失去了强引用,将会被GC自动回收,map中的所有entry项也将被回收。

4.2 内存泄露的场景

当使用线程池执行任务,核心线程一直存活,threadLocalMap一直被强引用。 当存在ThreadLocal强引用时,如果不主动调ThreadLocal.renove(),threadLocalMap中弱引用的key始终不能被gc。 此时将导致内存泄露。

4.3 避免内存泄露

当一个ThreadLocal不再使用时,主动调用remove()将它从ThreadLocalMap中移除。

ThreadLocal的set()、get()方法,也可能会触发清理失效数据。