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

原来 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,不是引用)。 线程池里的线程是池化的,起都起好了,不会每次复制。

你瞅瞅,这玩意简直比前任还迷惑:

  • 有时传,有时不传,情感极不稳定……

一点小总结:到底咋回事

核心点其实很简单:

特性ThreadLocalInheritableThreadLocal
变量范围当前线程当前线程 + 新建子线程(仅new Thread)
线程池行为线程独立线程独立,老线程不自动复制
数据可传递性不可跨线程只在 new 出来、刚启动的子线程里会变“遗传”

要注意:

  • 线程池基本不适用。因为线程池的线程通常不会因为你要变量才新创建,导致值不会自动同步过去。
  • 变量其实是内容浅拷贝,改了子线程的值,父线程没影响,反之亦然。

经验启示

写给下一位壮士的几点建议:

  1. 别乱用 InheritableThreadLocal,尤其别觉着可以在任何多线程环境自动搞定上下文传递。
  2. 要跨线程、线程池传递变量?老老实实用阿里开源的 TransmittableThreadLocal 或自己写逻辑显式传递!
  3. 真怕“泄露”——上下文别忘记移除,不然内存胖死你。
  4. 面试官问 InheritableThreadLocal 干嘛用的时候,记住这句金句:“适合短命线程,不适合线程池!”

写完这些随手抬头,才发现,被各路“ThreadLocal 儿孙”虐了一圈,只有自己最靠谱。切记: 上下文传播,Java 能帮的那点事,图省事你就输了。

好啦,今天的“ThreadLocal DNA 鉴定”就聊到这。放下代码,喝口水,远望天花板,继续在 Bug 的路上奋勇前行吧。

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