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 或拦截器清理。
笔记加载中…