原文来自于:zha-ge.cn/java/86

以为 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 哪是小白的糖

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]