Skip to content
极高困难

一句话答案

ThreadLocal 让每个线程持有变量的独立副本,底层用 ThreadLocalMap(key 是弱引用),用完必须 remove 防止内存泄漏。

核心要点

内存泄漏的根因:

Entry:Key(弱引用 → ThreadLocal 对象)+ Value(强引用 → 实际值)
强引用:ThreadLocal ref ──────────────────→ ThreadLocal 对象
弱引用:Entry.key ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ → ThreadLocal 对象

当外部 ThreadLocal ref 被置为 null 后:
  ThreadLocal 对象仅有弱引用(Entry.key),下次 GC 时被回收
  → Entry.key = null(弱引用断掉)

但 Entry.value 仍然被 ThreadLocalMap 强引用!
Thread → threadLocals → Entry[] → Entry.value(无法被回收)

内存泄漏成立的条件:

  1. ThreadLocal 变量没有外部强引用(被 GC 回收)
  2. 线程没有结束(如线程池中的线程长期存活)
  3. Entry.value 是大对象或大量积累

解决方案:

1. 使用完后立刻 remove()(最重要!)

java
ThreadLocal<UserContext> userContext = new ThreadLocal<>();
try {
    userContext.set(new UserContext(userId));
    // ... 业务逻辑
} finally {
    userContext.remove();  // 无论是否异常,都清理!
}

2. ThreadLocal 声明为 static(减少实例数量,不根治)

java
// 声明为 static,ThreadLocal 对象有类级别强引用,不会被 GC
// 但仍需 remove(),否则 value 仍然泄漏
private static final ThreadLocal<UserContext> USER_CTX = new ThreadLocal<>();

为什么 Entry.key 用弱引用而不用强引用?

  • 如果 key 是强引用:即使外部 ThreadLocal 变量置 null,Entry 仍持有强引用,ThreadLocal 对象和 value 都无法被 GC,泄漏更严重
  • 弱引用是一种"尽力而为"的设计:至少 ThreadLocal 对象本身可以被 GC
追问与易错

追问方向:

  • 为什么 ThreadLocalMap 的 key 是弱引用?(ThreadLocal 对象可以被 GC)
  • 既然 key 是弱引用为什么还会泄漏?(value 是强引用,key 被回收后 value 无法清理)
  • InheritableThreadLocal 是什么?(子线程继承父线程的 ThreadLocal 值)

易错点:

  • ❌ "ThreadLocal 是线程安全的变量"——它是线程隔离的变量,每个线程各自一份
  • ❌ "线程池 + ThreadLocal 不会泄漏"——线程复用时旧 value 不会自动清理,必须 remove

💡 记忆锚点

ThreadLocal 像每人一个储物柜(ThreadLocalMap),钥匙(key)是弱引用做的纸牌——钥匙丢了(GC 回收)柜子里的东西(value)还在,又没人来清理,就成了占着柜子的垃圾。线程池里线程不销毁,垃圾越攒越多——所以用完必须 remove(),自己清柜子。