原來(lái) ThreadLocal 還有“繼承版”?InheritableThreadLocal 真相揭秘!
原来 ThreadLocal 还有“继承版”?InheritableThreadLocal 真相揭秘!
身为老 Java 程序员,ThreadLocal 早就烂熟于心,可有次聊起多线程变量传递,居然被面试官出了道“你用过 InheritableThreadLocal 吗?”的骚操作。当场脑门一紧:啥?ThreadLocal 还有“亲儿子版”?其实我刚入行业的时候,老觉得:ThreadLocal 不就是让每个线程自个儿玩自个的吗?结果这几年业务越来越离谱,各种异步线程池、请求串联,突然发现真有点东西——
于是,这个“山寨 ThreadLocal”到底是个啥?用起来和老本尊真的一样吗?不啰嗦,咱聊点程序员的心里话。
那次糊涂:变量丢了
有回我在写个用户上下文,登录用户信息放 ThreadLocal,原以为安全得很:
threadLocal.set(userInfo); // 请求线程里塞进去
// ……业务处理……
结果写了个异步队列,子线程要调这个信息。嗯,结果好嘛,直接空指针打脸。原来线程变量还真没“遗传”到新开的线程里?(难道咱 ThreadLocal 这么绝情吗?)
当时的内心 OS 可以用一句话总结:
- “线程隔离,太特么彻底了!”
后来 Google 一下,发现江湖上居然流传着 InheritableThreadLocal 这么个“带血缘”的东西!
踩坑瞬间
说干就干,把 ThreadLocal 换成 InheritableThreadLocal,心想“变量终于能随儿子线程流淌了”。
private static final InheritableThreadLocal<User> userCtx =
new InheritableThreadLocal<>();
// 父线程塞进去
userCtx.set(user);
// 子线程再读
User u = userCtx.get();
- 如果你用
new Thread()真·新线程跑,嗬,还真能拿到。 - 但是你如果用线程池,刺激的事情发生了,变量压根没同步进来,还是 NULL!
又查查官方文档,才发现 InheritableThreadLocal 只有在线程刚创建、start 的时候,才会把父线程的变量复制一份给子线程(其实是 clone,不是引用)。
线程池里的线程是池化的,起都起好了,不会每次复制。
你瞅瞅,这玩意简直比前任还迷惑:
- 有时传,有时不传,情感极不稳定……
一点小总结:到底咋回事
核心点其实很简单:
| 特性 | ThreadLocal | InheritableThreadLocal |
|---|---|---|
| 变量范围 | 当前线程 | 当前线程 + 新建子线程(仅new Thread) |
| 线程池行为 | 线程独立 | 线程独立,老线程不自动复制 |
| 数据可传递性 | 不可跨线程 | 只在 new 出来、刚启动的子线程里会变“遗传” |
要注意:
- 线程池基本不适用。因为线程池的线程通常不会因为你要变量才新创建,导致值不会自动同步过去。
- 变量其实是内容浅拷贝,改了子线程的值,父线程没影响,反之亦然。
经验启示
写给下一位壮士的几点建议:
- 别乱用 InheritableThreadLocal,尤其别觉着可以在任何多线程环境自动搞定上下文传递。
- 要跨线程、线程池传递变量?老老实实用阿里开源的 TransmittableThreadLocal 或自己写逻辑显式传递!
- 真怕“泄露”——上下文别忘记移除,不然内存胖死你。
- 面试官问 InheritableThreadLocal 干嘛用的时候,记住这句金句:“适合短命线程,不适合线程池!”
写完这些随手抬头,才发现,被各路“ThreadLocal 儿孙”虐了一圈,只有自己最靠谱。切记:
上下文传播,Java 能帮的那点事,图省事你就输了。
好啦,今天的“ThreadLocal DNA 鉴定”就聊到这。放下代码,喝口水,远望天花板,继续在 Bug 的路上奋勇前行吧。
共同學(xué)習(xí),寫下你的評(píng)論
評(píng)論加載中...
作者其他優(yōu)質(zhì)文章