JDK的InheritableThreadLocal类能够完成父子线程值的传递。 但对于使用线程池等会缓存线程的组件的状况,线程由线程池建立好,而且线程是缓存起来反复使用的;这时父子线程关系的上下文传递已经没有意义,应用中要作上下文传递,其实是在把 任务提交给线程池时的上下文传递到 任务执行时。javascript
本库提供的TransmittableThreadLocal类继承并增强InheritableThreadLocal类,解决上述的问题,使用详见User Guide。html
欢迎java
建议和提问,提交Issuegit
贡献和改进,Fork后提经过Pull Request贡献代码github
在ThreadLocal的需求场景便是TTL的潜在需求场景,若是你的业务须要『在使用线程池等会缓存线程的组件状况下传递ThreadLocal』则是TTL目标场景。shell
下面是几个典型场景例子。api
分布式跟踪系统缓存
应用容器或上层框架跨应用代码给下层SDK传递信息架构
日志收集记录系统上下文oracle
各个场景的展开说明参见子文档 需求场景。
使用类TransmittableThreadLocal来保存上下文,并跨线程池传递。
TransmittableThreadLocal继承InheritableThreadLocal,使用方式也相似。
比InheritableThreadLocal,添加了protected方法copy,用于定制 任务提交给线程池时的上下文传递到 任务执行时时的拷贝行为,缺省是传递的是引用。
具体使用方式见下面的说明。
父线程给子线程传递值。
示例代码:
// 在父线程中设置
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>(); parent.set("value-set-in-parent"); // ===================================================== // 在子线程中能够读取, 值是"value-set-in-parent" String value = parent.get();
这是实际上是InheritableThreadLocal的功能,应该使用InheritableThreadLocal来完成。
但对于使用了异步执行(每每使用线程池完成)的状况,线程由线程池建立好,而且线程是缓存起来反复使用的。
这时父子线程关系的上下文传递已经没有意义,应用中要作上下文传递,其实是在把 任务提交给线程池时的上下文传递到任务执行时。 解决方法参见下面的这几种用法。
使用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.0.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_5一、1.7.0_45能够运行。
# 1.7.0_45仍是有JavaLaunchHelper的出错信息,但不影响运行。