适用读者:用过 ThreadLocal 但被"内存泄漏"警告吓到的人。
前置:S05-B01、S04-J01(WeakReference 相关)。
覆盖旧文:A-1117(508) A-1611(184) + 932(65) 933(63) 935(68) 934(59) = 947 点。
覆盖扩展:E-S05-006(弱引用与 Entry 泄漏)。
上一篇:S05-B03 · synchronized 与锁升级
下一篇:S05-B05 · 线程池。
1. 一句话结论
ThreadLocal 不是"线程的本地变量",而是"以线程为 key 的一张表"——表挂在 Thread 上(ThreadLocalMap),ThreadLocal 自己只是 key。
Thread ──► threadLocals: ThreadLocalMap
└── Entry[weak ref → ThreadLocal] ──► value(强引用)
2. 结构:为什么 key 是弱引用(E-S05-006)
| 引用 | 指向 | 类型 |
|---|---|---|
Thread → ThreadLocalMap |
表 | 强引用 |
Entry.key |
ThreadLocal 对象 | 弱引用 |
Entry.value |
业务值 | 强引用 |
设计意图:如果 ThreadLocal 对象不再被外部引用,弱引用会被 GC 回收 → key 变 null → 这是"防止 ThreadLocal 本身泄漏"的设计。
但 value 仍然是强引用:如果线程(尤其线程池里的长生命周期线程)不结束,Entry 不会被清理 → value 泄漏。
| 场景 | 是否泄漏 |
|---|---|
线程用完即销毁(new Thread) |
随线程结束释放,影响小 |
| 线程池复用线程 | 泄漏(value 一直挂在池线程上) |
3. 正确用法三条
| 规则 | 原因 |
|---|---|
用完必须 remove() |
清掉 value,避免池线程持有 |
用 try/finally 包住 |
保证异常时也清理 |
| 不要存大对象 | 泄漏放大 |
try {
ctx.set(user);
// 业务
} finally {
ctx.remove(); // ← 关键
}
框架层已在做:Spring 的 RequestContextHolder、事务上下文、日志 MDC 都在 finally 里 remove——它们不泄漏,是因为框架帮你清了。
4. 使用场景(A-934)
| 场景 | 为什么合适 |
|---|---|
| 请求上下文(用户/租户 ID) | 避免层层传参 |
| 事务/连接绑定 | 同线程共享同一连接 |
| 日志 MDC | 日志自动带上 traceId |
| 非线程安全对象的"每线程实例" | SimpleDateFormat(旧写法) |
反例:不要用 ThreadLocal 做"全局缓存"——它不是缓存,生命周期绑在线程上。
5. ThreadLocalMap 与 HashMap 的区别(A-935)
| 维度 | ThreadLocalMap | HashMap |
|---|---|---|
| 冲突处理 | 开放寻址(线性探测) | 链地址 |
| key | 弱引用 | 强引用 |
| 容量 | 固定 16 起,2 的幂 | 同 |
| 是否对外 | 私有 | 公开 |
为什么用开放寻址:Entry 数量少(一个线程通常只有几个),开放寻址更省内存、无需额外链表对象。
6. 五个误区
| 误区 | 真相 |
|---|---|
| "ThreadLocal 存的是线程私有变量" | 是"以线程为 key 的表" |
| "key 弱引用就不会泄漏" | value 是强引用,池线程会泄漏 |
| "用完不 remove 也没事" | 线程池场景必泄漏 |
| "ThreadLocal 可以用来做缓存" | 生命周期绑线程,不是缓存 |
| "子线程能继承父线程的 ThreadLocal" | 默认不能,需 InheritableThreadLocal |
7. 自检
- ThreadLocalMap 挂在哪?key 与 value 各是什么引用?
- 为什么 key 要用弱引用?这防的是什么泄漏?
- 线程池场景为什么会泄漏?怎么修?
ThreadLocalMap为什么用开放寻址而不是链表?- 什么场景不该用 ThreadLocal?
8. 下一篇
S05-B05 · 线程池:7 个参数、拒绝策略与动态调整
ThreadLocal 解决"不共享",线程池解决"别乱建"。下一篇用实测说明"核心 2 + 队列 2 为什么第 5 个任务被拒"。
本篇属于系列《S05 · Java 并发与 JVM》。旧文来源:A-1117/1611/932/933/935/934;扩展点 E-S05-006。