梦魇之王免安装绿色中文版
140M · 2025-09-26
原文来自于:zha-ge.cn/java/86
前几天,哥们儿约我喝咖啡(其实是吐槽),说他面试被问 ThreadLocal 的原理,答得头头是道,回头自己项目就翻了车。你说这人生多刺激。他问我:“ThreadLocal 都说是线程安全的,data 也存在线程自己的 Map 里,怎么就会泄漏内存?”我一听这话,脑子里差点想往墙上撞。号称最安全的 ThreadLocal,后面居然还有毒刺!
下面就聊聊我和 ThreadLocal 的迷幻之旅。
故事还要追溯到我第一次真正翻 ThreadLocal 源码。刚开始都觉得 ThreadLocal 超酷——给线程各自的小口袋,谁也别碰谁。是不是“分田到户”安全感拉满?你 set、我 get,干干净净的。
可是有一天我手抽点开 ThreadLocalMap,发现 key 居然是个弱引用(WeakReference)。当时那心情,跟发现新买的饮料其实是三无产品差不多:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
// value 是正常引用
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// ...
}
噢!ThreadLocal 作为 key,弱引用;value 却超坚强——普通的强引用,对象死死扒着。
你以为这事就这么完了?笑死我了。偏偏公司那破服务上线后内存警报就像大姨妈一样准时准点!
现象就像下表这样:
现象 | 描述 |
---|---|
内存暴涨 | Heap usage 两天就能怼满,FullGC 也救不回来 |
内存分析 | ThreadLocalMap 里 value 越堆越多,key 却是 null |
项目组集体懵逼 | “ThreadLocal 不是用完自动回收吗???” |
冷静点分析:
所以就算你的 key 都 GC 了,ThreadLocalMap 抱着 value 不撒手,value 泄漏最大赢家。
有几种骚操作,随便列几个:
小巧代码提醒自己:
threadLocal.set(userData);
// ...
try {
// ... 业务逻辑 ...
} finally {
threadLocal.remove(); // 必须!否则 value 还在内存里赖着不走
}
千万别心存侥幸,“懒得管又跑不了”是史上最危险的想法。
来,总结表:
概念 | 遇到的坑 | 最佳实践 |
---|---|---|
ThreadLocal 是安全 | value 会泄漏,不安全 | 用完及时 remove |
弱引用是双刃剑 | key 被 GC,value 留垃圾 | 不要缓存大对象 |
线程池难题 | 线程不死,垃圾就不死 | 资源手动清理 |
还有几个血泪教训:
瞎聊了这么多。技术栈的水,越觉得熟悉,越容易踩穿底裤。要我说,ThreadLocal 哪是小白的糖