使用线程封闭是实现线程安全最简单的方式之一,维持线程封闭性的一种常规方法是使用 ThreadLocal,它能使线程中的某个值与保存值的对象关联起来。ThreadLocal 提供了 get 与 set 等方法,这些方法为每个使用该变量的线程都有一份独立的副本,因此 get 总是返回由当前执行线程在调用 set 时设置的最新值。

实现原理

Thread 在内部定义了一个 ThreadLocal.ThreadLocalMap 类型的 threadLocals 变量,用于在 ThreadLocal 中获取当前线程关联的 ThreadLocal.ThreadLocalMap 实例。

  1. public class Thread implements Runnable {
  2. // ......
  3. /* ThreadLocal values pertaining to this thread. This map is maintained
  4. * by the ThreadLocal class. */
  5. ThreadLocal.ThreadLocalMap threadLocals = null;
  6. // ......
  7. }

ThreadLocal.ThreadLocalMap 在内部定义了一个 ThreadLocal.ThreadLocalMap.Entry 数组 table 变量。ThreadLocal.ThreadLocalMap.Entry 继承了 WeakReference<ThreadLocal<?>>,它使用 ThreadLocal 实例的引用作为 Map 的 key,使用 ThreadLocal set 方法设置的数据作为 Map 的 value。

  1. public class ThreadLocal<T> {
  2. // ......
  3. static class ThreadLocalMap {
  4. static class Entry extends WeakReference<ThreadLocal<?>> {
  5. Object value;
  6. Entry(ThreadLocal<?> k, Object v) {
  7. super(k);
  8. value = v;
  9. }
  10. }
  11. private Entry[] table;
  12. private void set(ThreadLocal<?> key, Object value) {
  13. Entry[] tab = table;
  14. // ......
  15. tab[i] = new Entry(key, value);
  16. // ......
  17. }
  18. // ......
  19. }
  20. // ......
  21. }

ThreadLocal 在对 ThreadLocal.ThreadLocalMap 进行封装之后,对外提供了 get 和 set 方法。

  1. public class ThreadLocal<T> {
  2. // ......
  3. public T get() {
  4. Thread t = Thread.currentThread();
  5. ThreadLocalMap map = getMap(t);
  6. if (map != null) {
  7. ThreadLocalMap.Entry e = map.getEntry(this);
  8. if (e != null) {
  9. @SuppressWarnings("unchecked")
  10. T result = (T)e.value;
  11. return result;
  12. }
  13. }
  14. return setInitialValue();
  15. }
  16. public void set(T value) {
  17. Thread t = Thread.currentThread();
  18. ThreadLocalMap map = getMap(t);
  19. if (map != null)
  20. map.set(this, value);
  21. else
  22. createMap(t, value);
  23. }
  24. // ......
  25. }

一图概览

  1. +------------+
  2. |Thread |
  3. |------------| +--------------------------+
  4. |threadLocals|-->|ThreadLocal.ThreadLocalMap|
  5. |------------| |--------------------------| +--------------------------------+
  6. | ...... | |table[] |-->|ThreadLocal.ThreadLocalMap.Entry|
  7. +------------+ |--------------------------| |-----------+-----------+--------|
  8. | ...... | |ThreadLocal|ThreadLocal| |
  9. +--------------------------+ | WeakRef | WeakRef | ...... |
  10. |-----------+-----------+--------|
  11. | value | value | ...... |
  12. +-----------+-----------+--------+

内存泄漏

ThreadThreadLocal 中都会持有 ThreadLocal.ThreadLocalMap 的引用,并且 ThreadLocal 内部与 ThreadLocal.ThreadLocalMap 的引用关系是 WeakReference 弱引用。所以在 ThreadLocal 已经被 GC 回收,并且当 Thread 还存活时,ThreadLocal.ThreadLocalMap 就是一个只会占用内存但不会被实际使用的内存垃圾。不过当 Thread 运行结束时,ThreadLocal.ThreadLocalMap 也会被回收,此时也就不存在内存泄漏的问题了。

在父子线程之间传递数据

InheritableThreadLocal 扩展了 ThreadLocal,支持在创建子线程时继承父线程中保存的 InheritableThreadLocal 数据。

向可复用的线程传递数据

ThreadLocal 中的数据与线程的生命周期绑定在一起,因此不适用于使用线程池等会池化复用线程的场景。但是如果应用确实使用了线程池 ThreadPoolExecutor,并且存在将 任务提交时 的数据传递给 任务执行时 的需求时候,可以考虑使用阿里巴巴的开源组件:TransmittableThreadLocal