0
点赞
收藏
分享

微信扫一扫

开源工具库收集-ALI 开源 Transmittable ThreadLocal(TTL)

 Transmittable ThreadLocal(TTL) 

 

​​ English Documentation​​ |  中文文档

  • 开源工具库收集-ALI 开源 Transmittable ThreadLocal(TTL)_线程池 功能​​
  • 开源工具库收集-ALI 开源 Transmittable ThreadLocal(TTL)_java_02 需求场景​​
  • ​​ User Guide​​
  • ​​1. 简单使用​​
  • ​​2. 保证线程池中传递值​​
  • ​​2.1 修饰​​Runnable​​和​​Callable​​​
  • ​​整个过程的完整时序图​​
  • ​​2.2 修饰线程池​​
  • ​​2.3 使用​​Java Agent​​来修饰​​JDK​​线程池实现类​​
  • ​​关于​​boot class path​​设置​​
  • ​​​Java​​的启动参数配置​​


  • 开源工具库收集-ALI 开源 Transmittable ThreadLocal(TTL)_alibaba_03 Java API Docs​​
  • 开源工具库收集-ALI 开源 Transmittable ThreadLocal(TTL)_线程池_04 Maven依赖​​
  • ​​ FAQ​​
  • 开源工具库收集-ALI 开源 Transmittable ThreadLocal(TTL)_jar_05 更多文档​​
  • 开源工具库收集-ALI 开源 Transmittable ThreadLocal(TTL)_线程池_06 相关资料​​
  • ​​Jdk Core Classes​​
  • 开源工具库收集-ALI 开源 Transmittable ThreadLocal(TTL)_jar_07 Contributors​​

 功能

开源工具库收集-ALI 开源 Transmittable ThreadLocal(TTL)_线程池_08 在使用线程池等会池化复用线程的执行组件情况下,提供​​​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​​​及其线程工厂的​​Wrapper​​),只有不到 1000 ​​SLOC​​代码行,非常精小。

欢迎 开源工具库收集-ALI 开源 Transmittable ThreadLocal(TTL)_java_09

  • 建议和提问,​​提交​​Issue​​​
  • 贡献和改进,​​​Fork​​后提通过​​Pull Request​​贡献代码​​

 需求场景

在​​ThreadLocal​​​的需求场景即是​​TTL​​​的潜在需求场景,如果你的业务需要『在使用线程池等会池化复用线程的执行组件情况下传递​​ThreadLocal​​​』则是​​TTL​​目标场景。

下面是几个典型场景例子。

  1. 分布式跟踪系统
  2. 日志收集记录系统上下文
  3. ​Session​​​级​​Cache​
  4. 应用容器或上层框架跨应用代码给下层​​SDK​​传递信息

各个场景的展开说明参见子文档 ​​需求场景​​。

 User Guide

使用类​​​TransmittableThreadLocal​​​来保存值,并跨线程池传递。

​​​TransmittableThreadLocal​​​​继承​​​InheritableThreadLocal​​​,使用方式也类似。

相比​​​InheritableThreadLocal​​​,添加了

  1. ​protected​​​方法​​copy​​ 用于定制 任务提交给线程池时 的​​ThreadLocal​​值传递到 任务执行时 的拷贝行为,缺省传递的是引用。
  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​​​。

整个过程的完整时序图

开源工具库收集-ALI 开源 Transmittable ThreadLocal(TTL)_java_10

 

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​
  • 修饰实现代码在​​​TtlExecutorTransformlet.java​​​。
  1. ​java.util.concurrent.ForkJoinTask​​​(对应的执行器组件是​​java.util.concurrent.ForkJoinPool​​)
  • 修饰实现代码在​​​TtlForkJoinTransformlet.java​​​。从版本​2.5.1​开始支持。
  • 注意:​​Java 8​​​引入的​​​CompletableFuture​​​​与(并行执行的)​​​Stream​​​​底层是通过​​ForkJoinPool​​​来执行,所以支持​​ForkJoinPool​​​后,​​TTL​​​也就透明支持了​​CompletableFuture​​​与​​Stream​​​。
  1. ​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 Agent​​规范 - ​​JavaDoc​​​
  • ​​JAR File Specification - JAR Manifest​​
  • ​​Working with Manifest Files - The Java™ TutorialsHide​​

​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.3</version> </dependency>

可以在 ​​search.maven.org​​ 查看可用的版本。

 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​​的出错信息,但不影响运行。

 更多文档

  • 开源工具库收集-ALI 开源 Transmittable ThreadLocal(TTL)_java_02 需求场景说明​​
  • 开源工具库收集-ALI 开源 Transmittable ThreadLocal(TTL)_alibaba_12 Developer Guide​​
  • ​​☔ 性能测试​​

 相关资料

Jdk Core Classes

  • ​​WeakHashMap​​
  • ​​InheritableThreadLocal​​

 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 :-)
举报

相关推荐

0 条评论