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

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

master
Go to file
Code

Latest commit

Merge pull request #204 #209

Bump spotbugs-annotations from 4.1.2 to 4.1.3
Bumps [spotbugs-annotations](https://github.com/spotbugs/spotbugs) from 4.1.2 to 4.1.3.
- [Release notes](https://github.com/spotbugs/spotbugs/releases)
- [Changelog](https://github.com/spotbugs/spotbugs/blob/master/CHANGELOG.md)
- [Commits](spotbugs/spotbugs@4.1.2...4.1.3)

Bump jacoco-maven-plugin from 0.8.5 to 0.8.6
Bumps [jacoco-maven-plugin](https://github.com/jacoco/jacoco) from 0.8.5 to 0.8.6.
- [Release notes](https://github.com/jacoco/jacoco/releases)
- [Commits](jacoco/jacoco@v0.8.5...v0.8.6)
9013071

Git stats

Files

Permalink
Failed to load latest commit information.

README.md

📌 TransmittableThreadLocal(TTL) 📌

Build Status Windows Build Status Coverage Status Maintainability
License Javadocs Maven Central GitHub release
Chat at gitter.im GitHub Stars GitHub Forks GitHub issues Percentage of issues still open

📖 English Documentation | 📖 中文文档



🔧 功能

👉 在使用线程池等会池化å¤?用线程的执行组件情况下,æ??供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。 一个Java标准库本应为框架/中间件设施开å?‘æ??供的标é…?能力,本库功能è?šç„¦ & 0ä¾?赖,支æŒ?Java 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<String>();
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<String>();
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,Callable的处ç?†ç±»ä¼¼

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<String>();
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<String>();
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<String>();
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)
    • 修饰实现代ç ?在TtlForkJoinTransformlet.java。从版本 2.5.1 开始支æŒ?。
    • 注æ„?:Java 8引入的CompletableFuture与(并行执行的)Stream底层是通过ForkJoinPoolæ?¥æ‰§è¡Œï¼Œæ‰€ä»¥æ”¯æŒ?ForkJoinPoolå?Žï¼ŒTTL也就é€?明支æŒ?了CompletableFuture与Stream。🎉
  3. java.util.TimerTask的å­?类(对应的执行器组件是java.util.Timer)
    • 修饰实现代ç ?在TtlTimerTaskTransformlet.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.x.jar=ttl.agent.enable.timer.task:true
      • -javaagent:path/to/transmittable-thread-local-2.x.x.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。

关于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.x.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的å?¯åЍå?‚æ•°é…?ç½®

在Java的å?¯åЍå?‚数加上:-javaagent:path/to/transmittable-thread-local-2.x.x.jar。

如果修改了下载的TTL的Jar的文件å??(transmittable-thread-local-2.x.x.jar),则需è¦?自己手动通过-Xbootclasspath JVMå?‚æ•°æ?¥æ˜¾å¼?é…?置:
比如修改文件å??æˆ?ttl-foo-name-changed.jar,则还加上Java的å?¯åЍå?‚数:-Xbootclasspath/a:path/to/ttl-foo-name-changed.jar

Java命令行示例如下:

java -javaagent:path/to/transmittable-thread-local-2.x.x.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 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.11.5</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

  • Mac OS X下,使用javaagent,å?¯èƒ½ä¼šæŠ¥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的出错信æ?¯ï¼Œä½†ä¸?å½±å“?è¿?行。

🗿 更多文档

📚 相关资料

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 :-)
You can’t perform that action at this time.