bigywater.tech
Java · 292

线程上下文传播

请求上下文(用户、trace id)放在 ThreadLocal 里,一进线程池就丢了?InheritableThreadLocal 为什么复用线程时拿到旧值?ScopedValue 和结构化并发怎么传?这篇逐个跑出来,还用了真实的 TTL 库。

复用即旧值
InheritableThreadLocal 在池线程里始终是线程创建那一刻的值
2 个版本
ScopedValue:JDK 21 要预览,JDK 27 不要;StructuredTaskScope 在 JDK 27 仍需预览
TTL 2.14.5
真实库:提交时值传递并在执行后恢复

ThreadLocal 与继承

实测 · 本机执行输出
      1 新建线程:TL=null ITL=req-1
      2 线程池第一次任务(线程此刻才被创建):TL=null ITL=req-1
      3 main 改成 req-2 后,线程池第二次任务(复用同一线程):TL=null ITL=req-1
      4 另一个新池,线程创建时 main 的 ITL=req-3:TL=null ITL=req-3
      5 main 移除之后,线程池 2 的线程里 ITL 还是:TL=null ITL=req-3
      6 子线程改它自己的副本后,main 的 ITL = null
      7 虚拟线程(继承默认开):TL=null ITL=req-7
      8 虚拟线程(inheritInheritableThreadLocals(false)):TL=null ITL=null

手工包装

实测 · 本机执行输出
      任务看到 TL=req-A
      任务看到 TL=req-B
      没有包装的任务看到 TL=null(包装版本在结束时已经恢复)

ScopedValue

实测 · 本机执行输出
  JDK 21:
  编译失败:
    <jtmp>/j152c/Main.java:3: error: ScopedValue is a preview API and is disabled by default.
      static final ScopedValue<String> USER = ScopedValue.newInstance();
                   ^
      (use --enable-preview to enable preview APIs)
    <jtmp>/j152c/Main.java:3: error: ScopedValue is a preview API and is disabled by default.
      static final ScopedValue<String> USER = ScopedValue.newInstance();
                                              ^
      (use --enable-preview to enable preview APIs)
    <jtmp>/j152c/Main.java:6: error: ScopedValue is a preview API and is disabled by default.
        ScopedValue.where(USER, "alice").run(() -> {
        ^
      (use --enable-preview to enable preview APIs)
    …(共 29 行)
  JDK 21 + --enable-preview:
      isBound(外面)=false;orElse=(无)
      里面 get()=alice
      嵌套重新绑定 get()=bob
      嵌套结束后 get()=alice
      普通子线程 isBound=false
      (没有异常)
      外面 get():java.util.NoSuchElementException
      where().call 返回值:CAROL
      ScopedValue 没有 set 方法(绑定不可变,只能用 where 重新绑定)
  JDK 27:
      isBound(外面)=false;orElse=(无)
      里面 get()=alice
      嵌套重新绑定 get()=bob
      嵌套结束后 get()=alice
      普通子线程 isBound=false
      (没有异常)
      外面 get():java.util.NoSuchElementException: ScopedValue not bound
      where().call 返回值:CAROL
      ScopedValue 没有 set 方法(绑定不可变,只能用 where 重新绑定)

结构化并发

实测 · 本机执行输出
  JDK 21 + --enable-preview:
      子任务看到 USER=alice,线程是虚拟线程=true
  JDK 27:
  编译失败:
    <jtmp>/j152d/Main.java:6: error: StructuredTaskScope is a preview API and is disabled by default.
          try (var scope = StructuredTaskScope.open()) {
                           ^
      (use --enable-preview to enable preview APIs)
    1 error
  JDK 27 + --enable-preview:
      子任务看到 USER=alice,线程是虚拟线程=true

TransmittableThreadLocal

实测 · 本机执行输出
      main 设置 req-1:未包装的池 ITL=null;TtlExecutors 包装的池 TTL=req-1
      main 设置 req-2:未包装的池 ITL=null;TtlExecutors 包装的池 TTL=req-2
      TtlRunnable 在线程 pool-1-thread-1 里看到 req-3
      TtlRunnable 执行完之后,未包装的池线程里 TTL 是:null(执行结束后被恢复);main 里是 req-4

分步演示

上下文传递
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12 与 JDK 27;TTL 2.14.5(Maven Central)
    • 没有验证:与 MDC(SLF4J/Logback)的集成、Spring 的 TaskDecorator、Reactor 的 Context、ScopedValue 在大量嵌套绑定下的性能、JDK 22–26 中 StructuredTaskScope API 的演变过程
    京ICP备2026067502号-1京公网安备11010502063487号