👉 在使用线程池等会缓存线程的组件情况下,提供ThreadLocal
值的传递功能,解决异步执行时上下文传递的问题。
JDK
的InheritableThreadLocal
类可以完成父线程到子线程的值传递。但对于使用线程池等会缓存线程的组件的情况,线程由线程池创建好,并且线程是缓存起来反复使用的;这时父子线程关系的ThreadLocal
值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时的ThreadLocal
值传递到 任务执行时。
本库提供的TransmittableThreadLocal
类继承并加强InheritableThreadLocal
类,解决上述的问题,使用详见User Guide。
欢迎 👏
- 建议和提问,提交
Issue
- 贡献和改进,
Fork
后提通过Pull Request
贡献代码
在ThreadLocal
的需求场景即是TTL
的潜在需求场景,如果你的业务需要『在使用线程池等会缓存线程的组件情况下传递ThreadLocal
』则是TTL
目标场景。
下面是几个典型场景例子。
- 分布式跟踪系统
- 应用容器或上层框架跨应用代码给下层
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
值传递到 任务执行时。
解决方法参见下面的这几种用法。
使用com.alibaba.ttl.TtlRunnable
和com.alibaba.ttl.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
ScheduledExecutorService
:修饰接口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
或是线程池的代码。即可以做到应用代码 无侵入,后面文档有结合实际场景的架构对这一点的说明。
示例代码:
// 框架代码
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();
parent.set("value-set-in-parent");
// 应用代码
ExecutorService executorService = Executors.newFixedThreadPool(3);
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();
Demo参见AgentDemo.java
。
目前Agent中,修饰了jdk
中的两个线程池实现类(实现代码在TtlTransformer.java
):
java.util.concurrent.ThreadPoolExecutor
java.util.concurrent.ScheduledThreadPoolExecutor
在Java
的启动参数加上:
-Xbootclasspath/a:/path/to/transmittable-thread-local-2.x.x.jar
-javaagent:/path/to/transmittable-thread-local-2.x.x.jar
注意:
- Agent修改是JDK的类,类中加入了引用
TTL
的代码,所以TTL Agent
的Jar
要加到bootclasspath
上。
Java命令行示例如下:
java -Xbootclasspath/a:transmittable-thread-local-2.0.0.jar \
-javaagent:transmittable-thread-local-2.0.0.jar \
-cp classes \
com.alibaba.ttl.threadpool.agent.demo.AgentDemo
有Demo演示『使用Java Agent来修饰线程池实现类』,执行工程下的脚本run-agent-demo.sh
即可运行Demo。
由于Runnable
和Callable
的修饰代码,是在线程池类中插入的。下面的情况会让插入的代码被绕过,传递会失效。
- 用户代码中继承
java.util.concurrent.ThreadPoolExecutor
和java.util.concurrent.ScheduledThreadPoolExecutor
, 覆盖了execute
、submit
、schedule
等提交任务的方法,并且没有调用父类的方法。
修改线程池类的实现,execute
、submit
、schedule
等提交任务的方法禁止这些被覆盖,可以规避这个问题。 - 目前,没有修饰
java.util.Timer
类,使用Timer
时,TTL
会有问题。
当前版本的Java API文档地址: http://alibaba.github.io/transmittable-thread-local/apidocs/
示例:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.1.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
的出错信息,但不影响运行。