ThreadLocal
java.lang.ThreadLocal<T>
是一个为线程提供线程局部变量的工具类。为线程提供一个线程私有的变量副本,这样多个线程都可以随意更改自己线程局部的变量,不会影响到其他线程。
首次调用threadLocal.get()
方法时会调用initialValue()
赋一个初始值。
例子: 1.8之前提供的SimpleDateFormat
不是线程安全的, 下面的代码用ThreadLocal 解决这个问题:
private final static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() { |
注: JDK1.8的 DateTimeFormatter是线程安全的.
实现
我们需要知道 Thread 类中有一个 Map 类型的 threadLocals
成员:
public class Thread { |
ThreadLocalMap 该类为一个采用线性探测法实现的 HashMap
(区别于 HashMap 的拉链法),
这个 HashMap 的 Entry 继承了 WeakReference<ThreadLocal<?>>
(类似 WeakHashMap)
调用 threadLocal.set(Val) 设置新对象时。实际是向 thread.ThreadLocalMap 插入对象,创建新的 Entry(K,V)
,K 即是 threadLocal 对象,并且从 Entry 的构造函数可以看到, K 是弱引用的,并且没有给弱引用设置一个回收用的 queue,这一点区别 WeakHashMap(Java-Tutorials.13.引用(Reference)),也就是说 threadLocal 并不会像 WeakHashMap 那样自动清理 Key 被回收掉的 Entry
// Entry的构造 |
从 ThreadLocal 中 get 值的时候,首先通过 Thread.currentThread()
得到当前线程,然后拿到这个线程的 ThreadLocalMap,从 Map 取得 Entry,最后从 Entry 取得中的 value 值;
ThreadLocal 的 set、get 、remove 方法最终都会调用 expungeStaleEntry(),找到已经被 GC 清理过的 Entry,将 value 置 null 帮助回收;
ThreadLocalMap 与 WeakHashMap 的比较:
- 二者的 Entry 都继承自 WeakReference,对 Key 的处理也都是弱引用;
- ThreadLocalMap 是开放定址,WeakHashMap 使用了链表
- ThreadLocalMap 没有为 Key 的弱引用指定 Queue,WeakHashMap 指定了 Queue,这也导致二者回收方式不同:
- WeakHashMap:
get()
put()
,size()
时通过 queue 获取被清理的 entry,设置 entry.value = null 帮助回收,@ref Java-Tutorials.13.引用(Reference) - ThreadLocalMap:见 expungeStaleEntry()
- WeakHashMap:
@ref: 一文详解JDK中的ThreadLocal(全网最透彻易懂) - 掘金
应用场景
ThreadLocal 适用方法调用链上参数的透传,但要注意是同线程间,但不适合异步方法调用的场景。对于异步方法调用,想做参数的透传可以采用阿里开源的 TransmittableThreadLocal。权限、日志、事务等框架都可以利用 ThreadLocal 透传重要参数。
使用 ThreadLocal 保存 Connection:
public class ConnectionManager { |
需要注意,因为 thread.ThreadLocalMap 的 Entry(k,v),其 K 是用 WeakReference 引用的,但是上面的代码中,K 是 static final
的强引用,所以每个线程 ThreadLocalMap 里的 Entry.value 都不会回收;上面的例子中 ThreadLocal 对象是 static 的,所以 key 也不会回收;
InheritableThreadLocal
如果在父线程中创建 ThreadLocal,会发现父线程设置的值在子线程中无法获取,JDK中有InheritableThreadLocal解决此问题。
public class SubThreadUsage { |
实现
InheritableThreadLocal 继承了 ThreadLocal
在 Thread 构造方法调用的 init() 中,可看见如果 parent.inheritableThreadLocals不为空,则 ThreadLocal.createInheritedMap()拷贝 ThreadLocalMap,注意这里的拷贝是浅拷贝。子线程如果修改了继承自父线程的ThreadLocal,其他的子线程也可能会看到这个改变。