米斯特里亚牧场免安装绿色版
424MB · 2025-11-04
原文来自于:zha-ge.cn/java/82
事情得从上个月公司的奇葩 bug 说起。那天下午刚吃完小龙虾回来,正准备摸鱼,结果 leader 一句“XX,接口日志乱掉了,赶紧看看!”把我头皮瞬间炸得比小龙虾还辣。
代码没啥难度,就是多线程下存点日志上下文。为了图方便,我们那位“热心老哥”直接搞了个全局变量,见谁都能改,天真地以为这样全世界都能用。这不,线程一多,日志就跟鬼画符似的,你一句我一句的,全搅和一块了。
事已至此,必须想点新招儿救火。搜了一圈资料,发现这玩意儿叫 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():线程池一用,线程复用,信息残留,日志串味。我当时就犯懒忘记 remove(),结果同一个线程跑第二个请求时,直接复用上一次的上下文,领导现场表演了一出“你是谁的马甲”。
不过真把 ThreadLocal 明白了之后,幸福感爆棚。再也不用像守护宝宝一样抢着管全局变量谁改了。别的线程改它一丁点儿,都和我没半毛钱关系。尤其是在线程池场景,自己记得 remove(),什么业务上下文、traceId、租户信息、AOP传递,全给我装进口袋。
还可以偷懒,给 ThreadLocal 加初始值:
private static final ThreadLocal<Integer> threadNum = ThreadLocal.withInitial(() -> 0);
public void increase() {
    threadNum.set(threadNum.get() + 1);
}
这样就算是什么都没 set,get 也是默认值,省心!
都说踩坑要踩透,下面这几条血泪经验,大家感受下:
| 推荐做法 | 踩坑大忌 | 
|---|---|
| 用完就 remove | 忘记清理 | 
| 只存轻量数据 | 搞大对象进 ThreadLocal | 
| 别跨线程用 | 以为全局可见 | 
反正 ThreadLocal 这小玩意儿,用得对能瞬间爽翻天,用得不对,分分钟翻车现场。总结下来一句话:管好自己的碗,少惦记别人的锅! 好,这篇碎碎念就到这,老板喊我写单测,下次有空再聊点花活。等你们踩过坑,也来吐槽几句,有空评论区见哈!
                            424MB · 2025-11-04
                            1.2G · 2025-11-04
                            283.2M · 2025-11-04