還在傻傻用全局變量?你知道 ThreadLocal 有多香嗎?
还在傻傻用全局变量?你知道 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 这小玩意儿,用得对能瞬间爽翻天,用得不对,分分钟翻车现场。总结下来一句话:管好自己的碗,少惦记别人的锅!
好,这篇碎碎念就到这,老板喊我写单测,下次有空再聊点花活。等你们踩过坑,也来吐槽几句,有空评论区见哈!
共同學(xué)習(xí),寫下你的評(píng)論
評(píng)論加載中...
作者其他優(yōu)質(zhì)文章