## **ThreadLocal 源码深入解析:从 set 到 get 的全链路剖析**

1. 引子:ThreadLocal 到底解决了什么问题?

多线程环境下,我们通常通过保证共享变量的线程安全。但锁有两大缺陷:

  1. 性能开销大
  2. 逻辑复杂,容易死锁

ThreadLocal 走了一条“避开共享”的路线:

给每个线程分配一份独立的变量副本,线程间互不干扰,从而避免同步。

它的理念是:共享变量 → 变为每个线程的私有变量

2. 使用示例

public class ThreadLocalDemo {
    private static final ThreadLocal<String> local = new ThreadLocal<>();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            local.set("T1's Value");
            System.out.println(Thread.currentThread().getName() + " -> " + local.get());
        }, "T1");

        Thread t2 = new Thread(() -> {
            local.set("T2's Value");
            System.out.println(Thread.currentThread().getName() + " -> " + local.get());
        }, "T2");

        t1.start();
        t2.start();
    }
}

输出:

T1 -> T1's Value
T2 -> T2's Value

两个线程的数据互不干扰。

3. 源码入口:set() 方法

JDK(以 OpenJDK 17 为例)ThreadLocal.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. Thread.currentThread()
    获取当前线程对象
  2. getMap(t)
    实际是 t.threadLocals,这是存储在线程对象里的 ThreadLocalMap
  3. 如果 map 存在,直接调用 map.set(this, value)
    this 就是当前的 ThreadLocal 实例
  4. 如果 map 不存在,调用 createMap() 创建

4. ThreadLocalMap 数据结构

Thread 类定义:

public class Thread implements Runnable {
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

也就是说,每个 Thread 自己维护一个 ThreadLocalMap,而不是 ThreadLocal 存储数据。

ThreadLocalMap 内部结构(简化):

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

    private Entry[] table;
}

要点:

  • key:ThreadLocal 对象(弱引用)
  • value:存储的实际数据(强引用)
  • Entry[] table:内部是一个线性探测法的哈希表

5. set 流程

ThreadLocalMap.set()

private void set(ThreadLocal<?> key, Object value) {
    int i = key.threadLocalHashCode & (table.length - 1);

    for (Entry e = table[i]; e != null; e = table[nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        if (k == key) { // 同一个 ThreadLocal
            e.value = value;
            return;
        }
        if (k == null) { // key 被 GC 回收
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    table[i] = new Entry(key, value);
}

流程:

  1. 计算哈希槽位 i
  2. 如果当前槽位已有相同 key,直接更新 value
  3. 如果 key == null(说明 ThreadLocal 对象被 GC 回收),走“过期替换”
  4. 否则插入新 Entry

6. get 流程

ThreadLocal.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();
}

  • map.getEntry(this):取当前 Thread 中这个 ThreadLocal 对应的 value
  • 如果没有,则调用 setInitialValue() 进行初始化(默认 null)

7. 内存泄漏隐患

由于 key 是弱引用,value 是强引用

  • ThreadLocal 对象如果没有外部强引用,会被 GC 回收(key → null)
  • 但 value 依然被强引用(在 Entry 中),直到线程结束才会释放
  • 如果是线程池(线程长期存活),这个 value 会一直占用内存 → 内存泄漏

源码中防泄漏的设计

  • get()set()remove() 中都有 expungeStaleEntry() 清理 key==null 的 Entry
  • 但如果不调用这些方法,value 依然残留

最佳实践

try {
    local.set(obj);
    // 业务逻辑
} finally {
    local.remove();
}

8.remove 方法

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null) {
        m.remove(this);
    }
}

清理当前线程的这个 ThreadLocal 的 Entry

彻底解除 key 和 value 的引用,防止内存泄漏

9.全流程原理图

Thread
 └── ThreadLocalMap (threadLocals)
       ├── Entry[0] → (WeakRef(ThreadLocalA), valueA)
       ├── Entry[1] → (WeakRef(ThreadLocalB), valueB)
       └── ...

10. 总结

  1. ThreadLocal 是线程封闭的实现方案,本质是在 Thread 中维护一份当前线程私有数据
  2. 核心是 Thread.threadLocals(ThreadLocalMap),数据存储在这里,而不是 ThreadLocal 自身。
  3. key 是弱引用,value 是强引用,使用完要 remove()
  4. 场景:数据库连接、用户上下文、非线程安全工具类实例等。

已生成图片

posted @ 2025-08-10 09:33  零1零1  阅读(16)  评论(0)    收藏  举报