## **ThreadLocal 源码深入解析:从 set 到 get 的全链路剖析**
1. 引子:ThreadLocal 到底解决了什么问题?
多线程环境下,我们通常通过锁保证共享变量的线程安全。但锁有两大缺陷:
- 性能开销大
- 逻辑复杂,容易死锁
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);
}
}
逐行解析:
Thread.currentThread()
获取当前线程对象getMap(t)
实际是t.threadLocals,这是存储在线程对象里的 ThreadLocalMap- 如果 map 存在,直接调用
map.set(this, value)
this 就是当前的 ThreadLocal 实例 - 如果 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);
}
流程:
- 计算哈希槽位
i - 如果当前槽位已有相同 key,直接更新 value
- 如果 key == null(说明 ThreadLocal 对象被 GC 回收),走“过期替换”
- 否则插入新 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. 总结
- ThreadLocal 是线程封闭的实现方案,本质是在 Thread 中维护一份当前线程私有数据。
- 核心是
Thread.threadLocals(ThreadLocalMap),数据存储在这里,而不是 ThreadLocal 自身。 - key 是弱引用,value 是强引用,使用完要
remove()。 - 场景:数据库连接、用户上下文、非线程安全工具类实例等。

浙公网安备 33010602011771号