跳转至

ThreadLocal 源码:线程本地副本原理

一、是什么

每个线程有自己的变量副本,多线程间互不影响。

ThreadLocal<String> tl = new ThreadLocal<>();
tl.set("hello");
tl.get();   // "hello"

二、结构

每个 Thread 对象内部有一个 ThreadLocalMap

class Thread {
    ThreadLocal.ThreadLocalMap threadLocals;
}

ThreadLocalMap 是个自定义哈希表:

ThreadLocalMap
  └── Entry[] table
        └── Entry extends WeakReference<ThreadLocal>
              ├── key: ThreadLocal(弱引用)
              └── value: 实际值

为什么 key 是弱引用

如果 key 是强引用,ThreadLocal 对象被回收后,Entry 还在,value 也在,造成内存泄漏。

弱引用:ThreadLocal 没别的引用时,GC 会回收 key,Entry 变 null。

三、set 流程

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
  1. 拿当前线程。
  2. 拿线程的 threadLocals。
  3. 以 ThreadLocal 对象为 key,存 value。

四、get 流程

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) return (T)e.value;
    }
    return setInitialValue();
}

五、内存泄漏

即使 key 是弱引用,value 是强引用。如果线程长期存在(线程池),Entry 的 key 被回收成 null,但 value 还在,没人访问,泄漏。

解决

用完手动 remove()

try {
    tl.set(value);
    // ...
} finally {
    tl.remove();
}

六、ThreadLocalMap 的 hash

threadLocalHashCode 递增,避免哈希冲突。不是常见的乘除法 hash。

高频考点

  • ThreadLocal 不是"线程隔离",是"每个线程一个 Map"。
  • 父子线程传值用 InheritableThreadLocal。
  • 线程池一定要 remove!