以為 ThreadLocal 很安全?key 的弱引用設(shè)計(jì)背后暗藏玄機(jī)!
以为 ThreadLocal 很安全?key 的弱引用设计背后暗藏玄机!
前几天,哥们儿约我喝咖啡(其实是吐槽),说他面试被问 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 不是用完自动回收吗???” |
冷静点分析:
- Thread 用完 ThreadLocal 后,ThreadLocal 变量被局部变量、成员变量都干掉了。
- 线程还没死(比如线程池里的线程根本不会死),ThreadLocalMap 还在。
- 反正 key 是弱引用,ThreadLocal 真成游魂野鬼了,但是 value——还是个击不死的小强!
所以就算你的 key 都 GC 了,ThreadLocalMap 抱着 value 不撒手,value 泄漏最大赢家。
后来是怎么解决的?
有几种骚操作,随便列几个:
- 一定要显示调用 remove(),自证清白。
- 别把 ThreadLocal 搞成 static,全局持有等于自杀。
- 有些人尝试用工具类自动清理……嗯,只能说靠运气。
- 线程池回收后,如果能重复利用就稳了,如果不能清掉 ThreadLocalMap,小心尸体越来越多!
小巧代码提醒自己:
threadLocal.set(userData);
// ...
try {
// ... 业务逻辑 ...
} finally {
threadLocal.remove(); // 必须!否则 value 还在内存里赖着不走
}
千万别心存侥幸,“懒得管又跑不了”是史上最危险的想法。
经验启示
来,总结表:
| 概念 | 遇到的坑 | 最佳实践 |
|---|---|---|
| ThreadLocal 是安全 | value 会泄漏,不安全 | 用完及时 remove |
| 弱引用是双刃剑 | key 被 GC,value 留垃圾 | 不要缓存大对象 |
| 线程池难题 | 线程不死,垃圾就不死 | 资源手动清理 |
还有几个血泪教训:
- ThreadLocalMap 的 entry 只有在下次 set 或 get 时才会“顺手”清理 value,闲着没活儿压根不帮你打扫卫生。
- 线程池环境里,记得给工具人善后,不然迟早要交学费。
- 总之一句话:线程安全背后,得自己多长点心!
瞎聊了这么多。技术栈的水,越觉得熟悉,越容易踩穿底裤。要我说,ThreadLocal 哪是小白的糖
共同學(xué)習(xí),寫下你的評(píng)論
評(píng)論加載中...
作者其他優(yōu)質(zhì)文章