📖 English Documentation | 📖 中文文档
👉 在使用线程池等会池化复用线程的组件情况下,提供ThreadLocal
值的传递功能,解决异步执行时上下文传递的问题。
一个Java
标准库本应为框架/中间件设施开发提供的标配能力,本库功能聚焦 & 0依赖,支持Java
13/12/11/10/9/8/7/6。
JDK
的InheritableThreadLocal
类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal
值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时的ThreadLocal
值传递到 任务执行时。
本库提供的TransmittableThreadLocal
类继承并加强InheritableThreadLocal
类,解决上述的问题,使用详见User Guide。
整个库包含TTL
核心功能(核心用户API
与框架/中间件的集成API
)、线程池修饰(ExecutorService
/ForkJoinPool
/TimerTask
)及其Java Agent
支持,只有不到 1000 SLOC
代码行,非常精小。
欢迎 👏
- 建议和提问,提交
Issue
- 贡献和改进,
Fork
后提通过Pull Request
贡献代码
在ThreadLocal
的需求场景即是TTL
的潜在需求场景,如果你的业务需要『在使用线程池等会池化复用线程的组件情况下传递ThreadLocal
』则是TTL
目标场景。
下面是几个典型场景例子。
- 分布式跟踪系统
- 日志收集记录系统上下文
Session
级Cache
- 应用容器或上层框架跨应用代码给下层
SDK
传递信息
各个场景的展开说明参见子文档 需求场景。
使用类TransmittableThreadLocal
来保存值,并跨线程池传递。
TransmittableThreadLocal
继承InheritableThreadLocal
,使用方式也类似。
相比InheritableThreadLocal
,添加了
protected
方法copy
用于定制 任务提交给线程池时 的ThreadLocal
值传递到 任务执行时 的拷贝行为,缺省传递的是引用。protected
方法beforeExecute
/afterExecute
执行任务(Runnable
/Callable
)的前/后的生命周期回调,缺省是空操作。
具体使用方式见下面的说明。
父线程给子线程传递值。
示例代码:
// 在父线程中设置
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();
parent.set("value-set-in-parent");
// =====================================================
// 在子线程中可以读取,值是"value-set-in-parent"
String value = parent.get();
这是其实是InheritableThreadLocal
的功能,应该使用InheritableThreadLocal
来完成。
但对于使用线程池等会池化复用线程的组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal
值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时的ThreadLocal
值传递到 任务执行时。
解决方法参见下面的这几种用法。
使用TtlRunnable
和TtlCallable
来修饰传入线程池的Runnable
和Callable
。
示例代码:
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();
parent.set("value-set-in-parent");
Runnable task = new Task("1");
// 额外的处理,生成修饰了的对象ttlRunnable
Runnable ttlRunnable = TtlRunnable.get(task);
executorService.submit(ttlRunnable);
// =====================================================
// Task中可以读取,值是"value-set-in-parent"
String value = parent.get();
上面演示了Runnable
,Callable
的处理类似
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();
parent.set("value-set-in-parent");
Callable call = new Call("1");
// 额外的处理,生成修饰了的对象ttlCallable
Callable ttlCallable = TtlCallable.get(call);
executorService.submit(ttlCallable);
// =====================================================
// Call中可以读取,值是"value-set-in-parent"
String value = parent.get();
省去每次Runnable
和Callable
传入线程池时的修饰,这个逻辑可以在线程池中完成。
通过工具类com.alibaba.ttl.threadpool.TtlExecutors
完成,有下面的方法:
getTtlExecutor
:修饰接口Executor
getTtlExecutorService
:修饰接口ExecutorService
getTtlScheduledExecutorService
:修饰接口ScheduledExecutorService
示例代码:
ExecutorService executorService = ...
// 额外的处理,生成修饰了的对象executorService
executorService = TtlExecutors.getTtlExecutorService(executorService);
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();
parent.set("value-set-in-parent");
Runnable task = new Task("1");
Callable call = new Call("2");
executorService.submit(task);
executorService.submit(call);
// =====================================================
// Task或是Call中可以读取,值是"value-set-in-parent"
String value = parent.get();
这种方式,实现线程池的传递是透明的,代码中没有修饰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 Task("1");
Callable call = new Call("2");
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
中的线程池实现如下:
java.util.concurrent.ThreadPoolExecutor
和java.util.concurrent.ScheduledThreadPoolExecutor
修饰实现代码在TtlExecutorTransformlet.java
java.util.concurrent.ForkJoinTask
(对应的线程池组件是java.util.concurrent.ForkJoinPool
)
修饰实现代码在TtlForkJoinTransformlet.java
java.util.TimerTask
的子类(对应的线程池组件是java.util.Timer
)
修饰实现代码在TtlTimerTaskTransformlet.java
注意:缺省没有开启TimerTask
的修饰,使用Agent
参数ttl.agent.enable.timer.task
开启:-javaagent:path/to/transmittable-thread-local-2.x.x.jar=ttl.agent.enable.timer.task:true
。
更多关于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。
因为修饰了JDK
的标准库的类,标准库由bootstrap class loader
加载;上面修饰后的JDK
类引用了TTL
的代码,所以TTL
的Jar
需要加到boot class path
上。
TTL
从v2.6.0
开始,加载TTL Agent
会自动把自己的Jar
设置到boot class path
上。
注意:不能修改从Maven
库下载的TTL
的Jar
的文件名(形如transmittable-thread-local-2.x.x.jar
)。
如果修改了,则需要自己手动通过-Xbootclasspath JVM
参数来显式配置(就像TTL
之前的版本的做法一样)。
实现是通过指定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 Agent
规范 -JavaDoc
- JAR File Specification - JAR Manifest
- Working with Manifest Files - The Java™ TutorialsHide
在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.ttl.threadpool.agent.demo.AgentDemo
或是
java -javaagent:path/to/ttl-foo-name-changed.jar \
-Xbootclasspath/a:path/to/ttl-foo-name-changed.jar \
-cp classes \
com.alibaba.ttl.threadpool.agent.demo.AgentDemo
当前版本的Java API文档地址: https://alibaba.github.io/transmittable-thread-local/apidocs/
示例:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.11.0</version>
</dependency>
可以在 search.maven.org 查看可用的版本。
- 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
的出错信息,但不影响运行。
- 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 :-)