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

还在傻傻用全局变量?你知道 ThreadLocal 有多香吗?

事情得从上个月公司的奇葩 bug 说起。那天下午刚吃完小龙虾回来,正准备摸鱼,结果 leader 一句“XX,接口日志乱掉了,赶紧看看!”把我头皮瞬间炸得比小龙虾还辣。

代码没啥难度,就是多线程下存点日志上下文。为了图方便,我们那位“热心老哥”直接搞了个全局变量,见谁都能改,天真地以为这样全世界都能用。这不,线程一多,日志就跟鬼画符似的,你一句我一句的,全搅和一块了。


ThreadLocal 初体验

事已至此,必须想点新招儿救火。搜了一圈资料,发现这玩意儿叫 ThreadLocal,翻译过来其实就是“线程本地变量”,可以让每个线程自顾自地存一份自己的数据,老死不相往来。想想也挺带感的,像开小灶一样互不影响。咋用呢?其实比你关空调还省事!

private static final ThreadLocal<String> logContext = new ThreadLocal<>();

public void handleRequest(String req) {
    logContext.set("请求ID: " + req);
    // ... 业务处理
    doBiz();
    logContext.remove();
}

看见没?set、get、remove,仨关键操作,比喝水还顺溜。


踩坑瞬间

当然,人生不可能一帆风顺,特别是写代码的时候。ThreadLocal 第一次真香警告没到三分钟,我就发现了陷阱:

  • 忘记 remove():线程池一用,线程复用,信息残留,日志串味。
  • 用错地方:非线程安全对象支棱不起来,反而更乱。
  • 误以为“全局可见”:实际上,线程间“各玩各的”,想在主线程里拿到子线程 ThreadLocal 数据?做梦吧!

我当时就犯懒忘记 remove(),结果同一个线程跑第二个请求时,直接复用上一次的上下文,领导现场表演了一出“你是谁的马甲”。


捅破天的 moment

不过真把 ThreadLocal 明白了之后,幸福感爆棚。再也不用像守护宝宝一样抢着管全局变量谁改了。别的线程改它一丁点儿,都和我没半毛钱关系。尤其是在线程池场景,自己记得 remove(),什么业务上下文、traceId、租户信息、AOP传递,全给我装进口袋。

还可以偷懒,给 ThreadLocal 加初始值:

private static final ThreadLocal<Integer> threadNum = ThreadLocal.withInitial(() -> 0);

public void increase() {
    threadNum.set(threadNum.get() + 1);
}

这样就算是什么都没 set,get 也是默认值,省心!


经验启示

都说踩坑要踩透,下面这几条血泪经验,大家感受下:

  • ThreadLocal 绝对不能忘记 remove,尤其在线程池里,不然内存泄漏分分钟。
  • 线程之间不可能共享 ThreadLocal 的值,甭做梦了。
  • 常见用途:日志 traceId、多租户 userId、AOP 跨层传递。
  • 用匿名内部类或 lamdba,初始化值很方便,但还是要记得清理。
推荐做法踩坑大忌
用完就 remove忘记清理
只存轻量数据搞大对象进 ThreadLocal
别跨线程用以为全局可见

反正 ThreadLocal 这小玩意儿,用得对能瞬间爽翻天,用得不对,分分钟翻车现场。总结下来一句话:管好自己的碗,少惦记别人的锅! 好,这篇碎碎念就到这,老板喊我写单测,下次有空再聊点花活。等你们踩过坑,也来吐槽几句,有空评论区见哈!

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