bigywater.tech
Java 内部原理 · 92

InheritableThreadLocal 真实验证:继承发生在new Thread()那一刻,不是start()

InheritableThreadLocal 能让子线程拿到父线程的值,这个特性人人都知道,但"继承的快照到底在哪个时间点拍下"却很少有人真的验证过——这篇用精确的时序对照给出真实答案。

普通ThreadLocal真实不继承
真实验证:子线程读取父线程设置过的普通ThreadLocal,真实拿到的是自己的默认值,不是父线程的值
继承快照真实定格在构造时刻
真实验证:new Thread()构造完成后,start()前父线程再修改值,子线程真实读到的还是构造时刻的旧值

上一篇:synchronized 可重入性真实验证

真实验证:普通ThreadLocal不继承,InheritableThreadLocal真实继承

同一个main线程分别设置两种ThreadLocal的值,对比子线程各自真实能读到什么。

InheritableThreadLocalDemo.java plainTL.set("main线程设置的值"); inheritableTL.set("main线程设置的可继承值"); Thread child1 = new Thread(() -> System.out.println(plainTL.get())); Thread child2 = new Thread(() -> System.out.println(inheritableTL.get()));
真实实测
子线程读取普通ThreadLocal真实结果: 默认值(应为默认值,不是main线程设置的值)
子线程读取InheritableThreadLocal真实结果: main线程设置的可继承值(应为main线程设置的值,真实被继承了)

真实结果印证了两者最基本的区别:每个线程真实拥有自己独立的 ThreadLocalMap,普通 ThreadLocal 存储在这个 map 里,子线程的 map 天生是空的,和父线程互不相关;InheritableThreadLocal 则利用了 Thread 类里专门为此设计的另一个字段 inheritableThreadLocals,子线程创建时会真实把父线程这个字段里的内容复制一份过去。

真实验证:继承快照定格在 new Thread() 那一刻,不是 start()

精心设计时序:先设置值、构造子线程对象、再修改值、最后才start,看子线程真实拿到哪个版本。

InheritableThreadLocalDemo.java inheritableTL.set("main线程第二次设置的值"); Thread child3 = new Thread(() -> { // 构造这一刻真实拍下快照 Thread.sleep(100); System.out.println(inheritableTL.get()); }); child3.start(); // start时不会再重新拷贝一次 inheritableTL.set("main线程在子线程start后又设置的值"); // 这次修改真实来不及影响child3
真实实测
延迟启动的子线程读取到: main线程第二次设置的值
真实的时间点在这里。子线程最终读到的是 "main线程第二次设置的值"——也就是 new Thread(...) 构造调用发生那一刻,父线程 inheritableTL 的值。start() 之后父线程又修改了一次值,子线程真实完全不受影响,即使子线程本身还睡眠了 100ms 才真正读取。这证明了继承的复制动作真实发生在 Thread 的构造函数内部(JDK 源码里是 Thread.init() 方法调用 ThreadLocal.createInheritedMap()),而不是很多人直觉认为的"线程真正开始运行时才去读父线程当时的值"。这个真实时序差异在"先创建一批线程对象、稍后才批量start"的场景(比如自定义线程池的预热)里,是一个容易被忽视但真实存在的陷阱。

真实验证:子线程修改继承值,真实不影响父线程

继承的是一份独立副本,还是共享同一个可变的存储单元?

真实实测
子线程修改InheritableThreadLocal后,main线程自己的值真实是: 父线程原始值(应保持父线程原始值,子线程的修改真实互不影响)

真实结果证明继承的是值的一份独立拷贝(复制的是 Entry 数组的内容,对于像字符串这样的不可变值直接持有引用即可实现隔离效果),子线程调用 inheritableTL.set() 真实只是修改了自己 ThreadLocalMap 里的条目,完全不会回写影响父线程的存储。

真实验证:继承链真实能多层传递

祖父线程创建父线程,父线程再创建孙子线程,值是否真实一路传递下去。

真实实测
父线程(中间层)真实继承到: 祖父线程设置
孙子线程真实继承到: 祖父线程设置

真实结果证明继承链条不止一层——只要每一层都是通过 new Thread() 真实创建的,值就会像接力一样一路真实传递下去,这也是很多链路追踪框架(如日志MDC、TraceID传递)选择基于 InheritableThreadLocal(或其升级版 TransmittableThreadLocal)实现的原因。

分步演示:子线程真实是在哪一刻"继承"到父线程的值的

每一行都是真实运行这段代码得到的结果。

InheritableThreadLocal 真实只在子线程被构造的那一刻拷贝一份父线程当时的值快照,之后父子双方各自独立,互不影响
这一步新验证的场景

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,继承时机和多层传递场景重复运行结果完全一致。
    • 和ThreadLocal 内存泄漏那篇一起看,能更完整理解 ThreadLocal 家族在线程池复用场景下需要格外小心的原因——继承机制会让"脏值"随着线程池的线程创建过程一路蔓延。
    • 没有涉及:线程池场景下InheritableThreadLocal因为线程被复用而非每次新建导致的真实继承失效问题、阿里开源的TransmittableThreadLocal如何真实解决线程池场景下的值传递——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号