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,子线程创建时会真实把父线程这个字段里的内容复制一份过去。
精心设计时序:先设置值、构造子线程对象、再修改值、最后才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线程第二次设置的值
继承的是一份独立副本,还是共享同一个可变的存储单元?
子线程修改InheritableThreadLocal后,main线程自己的值真实是: 父线程原始值(应保持父线程原始值,子线程的修改真实互不影响)
真实结果证明继承的是值的一份独立拷贝(复制的是 Entry 数组的内容,对于像字符串这样的不可变值直接持有引用即可实现隔离效果),子线程调用 inheritableTL.set() 真实只是修改了自己 ThreadLocalMap 里的条目,完全不会回写影响父线程的存储。
祖父线程创建父线程,父线程再创建孙子线程,值是否真实一路传递下去。
父线程(中间层)真实继承到: 祖父线程设置 孙子线程真实继承到: 祖父线程设置
真实结果证明继承链条不止一层——只要每一层都是通过 new Thread() 真实创建的,值就会像接力一样一路真实传递下去,这也是很多链路追踪框架(如日志MDC、TraceID传递)选择基于 InheritableThreadLocal(或其升级版 TransmittableThreadLocal)实现的原因。
每一行都是真实运行这段代码得到的结果。