ThreadLocal 的原理是什么?为什么会产生内存泄漏?
结论先行:ThreadLocal 提供线程局部变量——每个线程持有一份独立副本,互不干扰。原理是每个 Thread 内部维护一个 ThreadLocalMap,以 ThreadLocal 为 key、副本值为 value。由于 Entry 的 key 是弱引用,ThreadLocal 被回收后 value 仍被强引用链(Thread → ThreadLocalMap → Entry → value)持有,不清理就会内存泄漏,线程池复用线程时尤其严重。
结构要点
| 成员 | 引用类型 | 生命周期 |
|---|---|---|
| Thread.threadLocals | 强引用 | 随线程存活 |
| ThreadLocalMap.Entry.key | 弱引用 | ThreadLocal 无外部引用时即被回收 |
| Entry.value | 强引用 | 不 remove 则随线程一直存活 |
为什么会漏、怎么防
- 取值链:
thread.threadLocals以当前 ThreadLocal 为 key 查询,天然做到线程隔离。 - 泄漏成因:key 变为 null 后 Entry 不可访问,value 却仍被 Thread 强引用链持有,堆积成“脏 Entry”。
- 防御手段:业务线程用完调用
remove();get/set 过程也会顺带清理 key 为 null 的脏 Entry(启发式,不保证及时)。
记忆链路:Thread 活着 → Map 活着 → value 活着;key 虽是弱引用,也救不了强引用的 value。
public class ThreadLocalDemo {
private static final ThreadLocal<Integer> SEQ =
ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
Runnable task = () -> {
SEQ.set(SEQ.get() + 1); // 各线程副本互不干扰
System.out.println(Thread.currentThread().getName() + " -> " + SEQ.get());
SEQ.remove(); // 关键:线程池场景防止残留传染下一个任务
};
new Thread(task, "t1").start();
new Thread(task, "t2").start();
}
}
常见追问 / 记忆点
- 记忆点:key 弱引用防“ThreadLocal 本体泄漏”,value 泄漏必须靠手动 remove 兜底。
- 追问:线程池线程常驻,上一任务的 ThreadLocal 值会传染给下一任务,用完务必 remove。
- 追问:InheritableThreadLocal 让子线程继承父线程副本;阿里 TransmittableThreadLocal 解决线程池间的值传递。
- 追问:请求上下文、MDC 日志、Spring 事务资源都依赖 ThreadLocal;异步线程池场景要配合 finally 或拦截器清理。