The Wayback Machine - https://web.archive.org/web/20210412204955/https://github.com/alibaba/transmittable-thread-local
Skip to content

📌 TransmittableThreadLocal(TTL), the missing Java™ std lib(simple & 0-dependency) for framework/middleware, provide an enhanced InheritableThreadLocal that transmits values between threads even using thread pooling components.

master
Switch branches/tags
Go to file
Code

📌 TransmittableThreadLocal(TTL) 📌

Build Status Windows Build Status Coverage Status Maintainability JDK support
License Javadocs Maven Central GitHub release
Chat at gitter.im GitHub Stars GitHub Forks GitHub repo dependents GitHub issues GitHub Contributors

📖 English Documentation | 📖 中文文档



🔧 功能

👉 在使用线程池等会池化�?用线程的执行组件情况下,�??供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。 一个Java标准库本应为框架/中间件设施开�?��??供的标�?能力,本库功能�?�焦 & 0�?赖,支�?Java 17/16/15/14/13/12/11/10/9/8/7/6。

JDK的InheritableThreadLocal类�?�以完�?父线程到�?线程的值传递。但对于使用线程池等会池化�?用线程的执行组件的情况,线程由线程池创建好,并且线程是池化起�?��??�?使用的;这时父�?线程关系的ThreadLocal值传递已�?没有�?义,应用需�?的实际上是把 任务�??交给线程池时的ThreadLocal值传递到 任务执行时。

本库�??供的TransmittableThreadLocal类继承并加强InheritableThreadLocal类,解决上述的问题,使用详�? User Guide。

整个TransmittableThreadLocal库的核心功能(用户API与框架/中间件的集�?API�?线程池ExecutorService/ForkJoinPool/TimerTask�?�其线程工厂的Wrapper),�?�有 ~1000 SLOC代�?行,�?�常精�?。

欢迎 �?

🎨 需求场景

ThreadLocal的需求场景�?�TransmittableThreadLocal的潜在需求场景,如果你的业务需�?『在使用线程池等会池化�?用线程的执行组件情况下传递ThreadLocal值�?则是TransmittableThreadLocal目标场景。

下�?�是几个典型场景例�?。

  1. 分布�?跟踪系统 或 全链路压测(�?�链路打标)
  2. 日志收集记录系统上下文
  3. Session级Cache
  4. 应用容器或上层框架跨应用代�?给下层SDK传递信�?�

�?�个场景的展开说明�?��?�?文档 需求场景。

👥 User Guide

使用类TransmittableThreadLocal�?��?存值,并跨线程池传递。

TransmittableThreadLocal继承InheritableThreadLocal,使用方�?也类似。相比InheritableThreadLocal,添加了

  1. copy方法
    用于定制 任务�??交给线程池时 的ThreadLocal值传递到 任务执行时 的拷�?行为,缺�?传递的是引用。
    注�?:如果跨线程传递了对象引用因为�?�?有线程�?闭,与InheritableThreadLocal.childValue一样,使用者/业务逻辑�?注�?传递对象的线程安全。
  2. protected的beforeExecute/afterExecute方法
    执行任务(Runnable/Callable)的�?/�?�的生命周期回调,缺�?是空�?作。

具体使用方�?�?下�?�的说明。

1. 简�?�使用

父线程给�?线程传递值。

示例代�?:

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

// =====================================================

// 在父线程中设置
context.set("value-set-in-parent");

// =====================================================

// 在�?线程中�?�以读�?�,值是"value-set-in-parent"
String value = context.get();

# 完整�?��?行的Demo代�?�?��?SimpleDemo.kt。

这其实是InheritableThreadLocal的功能,应该使用InheritableThreadLocal�?�完�?。

但对于使用线程池等会池化�?用线程的执行组件的情况,线程由线程池创建好,并且线程是池化起�?��??�?使用的;这时父�?线程关系的ThreadLocal值传递已�?没有�?义,应用需�?的实际上是把 任务�??交给线程池时的ThreadLocal值传递到 任务执行时。

解决方法�?��?下�?�的这几�?用法。

2. �?�?线程池中传递值

2.1 修饰Runnable和Callable

使用TtlRunnable和TtlCallable�?�修饰传入线程池的Runnable和Callable。

示例代�?:

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

// =====================================================

// 在父线程中设置
context.set("value-set-in-parent");

Runnable task = new RunnableTask();
// �?外的处�?�,生�?修饰了的对象ttlRunnable
Runnable ttlRunnable = TtlRunnable.get(task);
executorService.submit(ttlRunnable);

// =====================================================

// Task中�?�以读�?�,值是"value-set-in-parent"
String value = context.get();

注�?:
�?�使是�?�一个Runnable任务多次�??交到线程池时,�?次�??交时都需�?修饰�?作(�?�TtlRunnable.get(task)),以抓�?�当时TransmittableThreadLocal上下文的值;�?�如果�?�一个任务下一次�??交时�?执行修饰而�?然使用上一次的TtlRunnable,则�??交的任务�?行时会是上次抓�?�的上下文。示例代�?如下:

// 第一次�??交
Runnable task = new RunnableTask();
executorService.submit(TtlRunnable.get(task));

// ...业务逻辑代�?,
// 并且修改了 TransmittableThreadLocal上下文 ...
// context.set("value-modified-in-parent");

// �?次�??交
// �?新执行修饰,以传递修改了的 TransmittableThreadLocal上下文
executorService.submit(TtlRunnable.get(task));

上�?�演示了Runnable,Callable的处�?�类似

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

// =====================================================

// 在父线程中设置
context.set("value-set-in-parent");

Callable call = new CallableTask();
// �?外的处�?�,生�?修饰了的对象ttlCallable
Callable ttlCallable = TtlCallable.get(call);
executorService.submit(ttlCallable);

// =====================================================

// Call中�?�以读�?�,值是"value-set-in-parent"
String value = context.get();

# 完整�?��?行的Demo代�?�?��?TtlWrapperDemo.kt。

整个过程的完整时�?图

时�?图

2.2 修饰线程池

�?去�?次Runnable和Callable传入线程池时的修饰,这个逻辑�?�以在线程池中完�?。

通过工具类com.alibaba.ttl.threadpool.TtlExecutors完�?,有下�?�的方法:

  • getTtlExecutor:修饰接�?�Executor
  • getTtlExecutorService:修饰接�?�ExecutorService
  • getTtlScheduledExecutorService:修饰接�?�ScheduledExecutorService

示例代�?:

ExecutorService executorService = ...
// �?外的处�?�,生�?修饰了的对象executorService
executorService = TtlExecutors.getTtlExecutorService(executorService);

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

// =====================================================

// 在父线程中设置
context.set("value-set-in-parent");

Runnable task = new RunnableTask();
Callable call = new CallableTask();
executorService.submit(task);
executorService.submit(call);

// =====================================================

// Task或是Call中�?�以读�?�,值是"value-set-in-parent"
String value = context.get();

# 完整�?��?行的Demo代�?�?��?TtlExecutorWrapperDemo.kt。

2.3 使用Java Agent�?�修饰JDK线程池实现类

这�?方�?,实现线程池的传递是�?明的,业务代�?中没有修饰Runnable或是线程池的代�?。�?��?�以�?�到应用代�? 无侵入。
# 关于 无侵入 的更多说明�?��?文档Java Agent方�?对应用代�?无侵入。

示例代�?:

// ## 1. 框架上层逻辑,�?�续�?程框架调用业务 ##
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
context.set("value-set-in-parent");

// ## 2. 应用逻辑,�?�续�?程业务调用框架下层逻辑 ##
ExecutorService executorService = Executors.newFixedThreadPool(3);

Runnable task = new RunnableTask();
Callable call = new CallableTask();
executorService.submit(task);
executorService.submit(call);

// ## 3. 框架下层逻辑 ##
// Task或是Call中�?�以读�?�,值是"value-set-in-parent"
String value = context.get();

Demo�?��?AgentDemo.kt。执行工程下的脚本scripts/run-agent-demo.sh�?��?��?行Demo。

目�?TTL Agent中,修饰了的JDK执行器组件(�?�如线程池)如下:

  1. java.util.concurrent.ThreadPoolExecutor 和 java.util.concurrent.ScheduledThreadPoolExecutor
  2. java.util.concurrent.ForkJoinTask(对应的执行器组件是java.util.concurrent.ForkJoinPool)
    • 修饰实现代�?在ForkJoinTtlTransformlet.java。从版本 2.5.1 开始支�?。
    • 注�?:Java 8引入的CompletableFuture与(并行执行的)Stream底层是通过ForkJoinPool�?�执行,所以支�?ForkJoinPool�?�,TTL也就�?明支�?了CompletableFuture与Stream。🎉
  3. java.util.TimerTask的�?类(对应的执行器组件是java.util.Timer)
    • 修饰实现代�?在TimerTaskTtlTransformlet.java。从版本 2.7.0 开始支�?。
    • 注�?:从2.11.2版本开始缺�?开�?�TimerTask的修饰(因为�?�?正确性是第一�?,而�?是最佳实践『�?推�??使用TimerTask�?:);2.11.1版本�?�其之�?的版本没有缺�?开�?�TimerTask的修饰。
    • 使用Agent�?�数ttl.agent.enable.timer.task开�?�/关闭TimerTask的修饰:
      • -javaagent:path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.enable.timer.task:true
      • -javaagent:path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.enable.timer.task:false
    • 更多关于TTL Agent�?�数的�?置说明详�?TtlAgent.javaçš„JavaDoc。

关于java.util.TimerTask/java.util.Timer

Timer是JDK 1.3的�?类,�?推�??使用Timer类。

推�??用ScheduledExecutorService。
ScheduledThreadPoolExecutor实现更强壮,并且功能更丰富。 如支�?�?置线程池的大�?(Timer�?�有一个线程);Timer在Runnable中抛出异常会中止定时执行。更多说明�?��?10. Mandatory Run multiple TimeTask by using ScheduledExecutorService rather than Timer because Timer will kill all running threads in case of failing to catch exceptions. - Alibaba Java Coding Guidelines。

Java Agent的�?�动�?�数�?置

在Java的�?�动�?�数加上:-javaagent:path/to/transmittable-thread-local-2.x.y.jar。

注�?:

  • 如果修改了下载的TTLçš„Jar的文件�??(transmittable-thread-local-2.x.y.jar),则需�?自己手动通过-Xbootclasspath JVM�?�数�?�显�?�?置。
    比如修改文件�??�?ttl-foo-name-changed.jar,则还需�?加上Java的�?�动�?�数:-Xbootclasspath/a:path/to/ttl-foo-name-changed.jar。
  • 或使用v2.6.0之�?的版本(如v2.5.1),则也需�?自己手动通过-Xbootclasspath JVM�?�数�?�显�?�?置(就�?TTL之�?的版本的�?�法一样)。
    加上Java的�?�动�?�数:-Xbootclasspath/a:path/to/transmittable-thread-local-2.5.1.jar。

Java命令行示例如下:

java -javaagent:path/to/transmittable-thread-local-2.x.y.jar \
    -cp classes \
    com.alibaba.demo.ttl.agent.AgentDemo

# 如果修改了TTL jar文件�?? 或 TTL版本是 2.6.0 之�?
# 则还需�?显�?设置 -Xbootclasspath �?�数
java -javaagent:path/to/ttl-foo-name-changed.jar \
    -Xbootclasspath/a:path/to/ttl-foo-name-changed.jar \
    -cp classes \
    com.alibaba.demo.ttl.agent.AgentDemo

java -javaagent:path/to/transmittable-thread-local-2.5.1.jar \
    -Xbootclasspath/a:path/to/transmittable-thread-local-2.5.1.jar \
    -cp classes \
    com.alibaba.demo.ttl.agent.AgentDemo

关于boot class path

因为修饰了JDK标准库的类,标准库由bootstrap class loader加载;修饰�?�的JDK类引用了TTL的代�?,所以Java Agent使用方�?下TTL Jar文件需�?�?置到boot class path上。

TTL从v2.6.0开始,加载TTL Agent时会自动设置TTL Jar到boot class path上。
注�?:�?能修改从Maven库下载的TTL Jar文件�??(形如transmittable-thread-local-2.x.y.jar)。 如果修改了,则需�?自己手动通过-Xbootclasspath JVM�?�数�?�显�?�?置(就�?TTL之�?的版本的�?�法一样)。

自动设置TTL Jar到boot class path的实现是通过指定TTL Java Agent Jar文件里manifest文件(META-INF/MANIFEST.MF)的Boot-Class-Path属性:

Boot-Class-Path

A list of paths to be searched by the bootstrap class loader. Paths represent directories or libraries (commonly referred to as JAR or zip libraries on many platforms). These paths are searched by the bootstrap class loader after the platform specific mechanisms of locating a class have failed. Paths are searched in the order listed.

更多详�?

🔌 Java API Docs

当�?版本的Java API文档地�?�: https://alibaba.github.io/transmittable-thread-local/apidocs/

�?� Maven�?赖

示例:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
    <version>2.12.1</version>
</dependency>

�?�以在 search.maven.org 查看�?�用的版本。

🔨 关于编译构建与IDE开�?�

编译构建的环境�?求: JDK 8~11;用Maven常规的方�?执行编译构建�?��?�:
# 在工程中已�?包�?�了符�?�版本�?求的Maven,直接�?行 工程根目录下的mvnw;并�?需�?先手动自己安装好Maven。

# �?行测试Case
./mvnw test
# 编译打包
./mvnw package
# �?行测试Case�?编译打包�?安装TTL库到Maven本地
./mvnw install

#####################################################
# 如果使用你自己安装的 maven,版本�?求:maven 3.3.9+
mvn install

如何用IDE�?�开�?�时注�?点,更多说明�?��? 文档 如何用IDE开�?� - Developer Guide。

�?� FAQ

Q1. TTL Agent与其它Agent(如Skywalking�?Promethues)�?�?�使用时�?生效?

�?置TTL Agent在最�?的�?置,�?�以�?��?与其它其它Agent�?�?�使用时,TTL Agent�?�能的�?生效问题。�?置示例:

java -javaagent:path/to/transmittable-thread-local-2.x.y.jar \
     -javaagent:path/to/skywalking-agent.jar \
     -jar your-app.jar

原因是:

  • �?Skywalking这样的Agent的入�?�逻辑(premain)包�?�了线程池的�?�动。
  • 如果�?置在这样的Agent�?置在�?�?�,到了TTL Agent(的premain)时,TTL需�?加强的线程池类已�?加载(load)了。
  • TTL Agentçš„TtlTransformer是在类加载时触�?�类的增强;如果类已�?加载了会跳过TTL Agent的增强逻辑。

更多讨论�?��? Issue:TTL agent与其他Agent的兼容性问题 #226。

Q2. MacOS下,使用Java Agent,�?�能会报JavaLaunchHelper的出错信�?�

JDK Bug: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8021205
�?�以�?�一个版本的JDK。我的开�?�机上1.7.0_40有这个问题,1.6.0_51�?1.7.0_45�?�以�?行。
# 1.7.0_45还是有JavaLaunchHelper的出错信�?�,但�?影�?�?行。

✨ 使用TTL的好处与必�?性

注:�?读这一节,并�?会影�?你使用TTL�?�解决你碰到的问题,�?�以放心跳过;读了 User Guide 就�?�以快速用起�?�了~ 😄 这一节信�?�密度较高�?易读。

好处:�?明且自动完�?所有异步执行上下文的�?�定制�?规范化的�?��?�与传递。
这个好处也是TransmittableThreadLocal的目标。

必�?性:�?�?�应用的分布�?微�?务化并使用�?��?中间件,越�?�越多的功能与组件会涉�?��?�?�的上下文,逻辑�?程也越�?�越长;上下文问题实际上是个大的易错的架构问题,需�?统一的对业务�?明的解决方案。

使用ThreadLocal作为业务上下文传递的�?典技术手段在中间件�?技术与业务框架中广泛大�?使用。而对于生产应用,几乎一定会使用线程池等异步执行组件,以高效支撑线上大�?�?。但使用ThreadLocal�?�其set/remove的上下文传递模�?,在使用线程池等异步执行组件时,存在多方�?�的问题:

1. 从业务使用者角度�?�看

  1. �?�??
    • 业务逻辑�?知�?�:有哪些上下文;�?�个上下文是如何获�?�的。
    • 并需�?业务逻辑去一个一个地�?��?�与传递。
  2. �?赖
    • 需�?直接�?赖�?�?�ThreadLocal上下文�?�自的获�?�的逻辑或类。
    • �?RPC的上下文(如Dubboçš„RpcContext)�?全链路跟踪的上下文(如SkyWalkingçš„ContextManager)�?�?�?�业务模�?�中的业务�?程上下文,等等。
  3. �?��?(易�?)
    • 因为�? 事先 知�?�有哪些上下文,如果系统出现了一个新的上下文,业务逻辑就�?修改添加上新上下文传递的几行代�?。也就是说因 系统的 上下文新增,业务的 逻辑就跟进�?修改。
    • 而对于业务�?�说,�?关心系统的上下文,�?�往往就�?�能�?��?,会是线上故障了。
    • �?�?�应用的分布�?微�?务化并使用�?��?中间件,越�?�越多的功能与组件会涉�?��?�?�的上下文,逻辑�?程也越�?�越长;上下文问题实际上是个大的易错的架构问题,需�?统一的对业务�?明的解决方案。
  4. 定制性
    • 因为需�?业务逻辑�?�完�?�?��?�与传递,业务�?关注『上下文的传递方�?�?:直接传引用?还是拷�?传值?拷�?是深拷�?还是浅拷�??在�?�?�的上下文会需�?�?�?�的�?�法。
    • 『上下文的传递方�?�?往往是 上下文的�??供者(或说是业务逻辑的框架部分)�?能决策处�?�好的;而 上下文的使用者(或说是业务逻辑的应用部分)往往�?(期望)知�?�上下文的传递方�?。这也�?�以�?�解�?是 �?赖,�?�业务逻辑 �?èµ–/关注/实现了 系统/架构的『上下文的传递方�?�?。

2. 从整体�?程实现角度�?�看

关注的是 上下文传递�?程的规范化。上下文传递到了�?线程�?�?�好 清�?�(或更准确地说是�? �?��? �?之�?的上下文),需�?业务逻辑去处�?�好。如果业务逻辑对清�?�的处�?��?正确,比如:

  • 如果清�?��?作�?了:
    • 下一次执行�?�能是上次的,�?�『上下文的 污染/串�?��?,会导致业务逻辑错误。
    • 『上下文的 泄�?�?,会导致内存泄�?问题。
  • 如果清�?��?作�?�多了,会出现上下文 丢失。

上�?�的问题,在业务开�?�中引�?�的Bug真是屡�?�?鲜 �?本质原因是:ThreadLocal的set/remove的上下文传递模�? 在使用线程池等异步执行组件的情况下�?�?是有效的。常�?的典型例�?:

  • 当线程池满了且线程池的RejectedExecutionHandler使用的是CallerRunsPolicy时,�??交到线程池的任务会在�??交线程中直接执行,ThreadLocal.remove�?作清�?��??交线程的上下文导致上下文丢失。
  • 类似的,使用ForkJoinPool(包�?�并行执行Stream与CompletableFuture,底层使用ForkJoinPool)的场景,展开的ForkJoinTask会在任务�??交线程中直接执行。�?�样导致上下文丢失。

怎么设计一个『上下文传递�?程�?方案(�?�上下文的生命周期),以�?�?没有上�?�的问题?

期望:上下文生命周期的�?作从业务逻辑中分离出�?�。业务逻辑�?涉�?�生命周期,就�?会有业务代�?如�?忽清�?�而引�?�的问题了。整个上下文的传递�?程或说生命周期�?�以规范化�?:�?��?��?回放和�?��?这3个�?作,�?�CRR(capture/replay/restore)模�?。更多讨论�?��? Issue:能在详细讲解一下replay�?restore的设计�?�念�?�?#201。

总结上�?�的说明:在生产应用(几乎一定会使用线程池等异步执行组件)中,使用ThreadLocal�?�其set/remove的上下文传递模�?几乎一定是有问题的,�?�是在等一个出Bug的机会。

更多TTL好处与必�?性的展开讨论�?��? Issue:这个库带�?�怎样的好处和优势? #128,欢迎继续讨论 ♥�?

🗿 更多文档

📚 相关资料

JDK Core Classes

👷 Contributors

  • Jerry Lee <oldratlee at gmail dot com> @oldratlee
  • Yang Fang <snoop.fy at gmail dot com> @driventokill
  • Zava Xu <zava.kid at gmail dot com> @zavakid
  • wuwen <wuwen.55 at aliyun dot com> @wuwen5
  • Xiaowei Shi <179969622 at qq dot com> @xwshiustc
  • David Dai <351450944 at qq dot com> @LNAmp
  • Your name here :-)