Transmittable ThreadLocal(TTL) 正在参加 2020 年度 OSC 中国开源项目评选,请投票支持!
Transmittable ThreadLocal(TTL) 在 2020 年度 OSC 中国开源项目评选 中已获得 {{ projectVoteCount }} 票,请投票支持!
投票让它出道
已投票
Transmittable ThreadLocal(TTL) 获得 2020 年度 OSC 中国开源项目评选「最佳人气项目」 !
Transmittable ThreadLocal(TTL) 获得 2020 年度 OSC 中国开源项目评选「最佳人气项目」「最积极运营项目」 !
Transmittable ThreadLocal(TTL) 获得 2020 年度 OSC 中国开源项目评选「最积极运营项目」 !
授权协议 Apache
开发语言 Java 查看源码 »
操作系统 跨平台
软件类型 开源软件
所属分类 程序开发缓存系统
开源组织 阿里巴巴
地区 不详
投 递 者 oldratlee
适用人群 未知
收录时间 2013-12-05

软件简介

在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。 一个Java标准库本应为框架/中间件设施开发提供的标配能力,本库功能聚焦 & 0依赖,支持Java 13/12/11/10/9/8/7/6。

JDKInheritableThreadLocal类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的执行组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时ThreadLocal值传递到 任务执行时

本库提供的TransmittableThreadLocal类继承并加强InheritableThreadLocal类,解决上述的问题,使用详见User Guide

整个TTL库的核心功能(用户API与框架/中间件的集成API、线程池ExecutorService/ForkJoinPool/TimerTask及其线程工厂的Wrapper),只有不到 1000 SLOC代码行,非常精小。

欢迎 👏

🎨 需求场景

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

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

  1. 分布式跟踪系统
  2. 日志收集记录系统上下文
  3. SessionCache
  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 修饰RunnableCallable

使用TtlRunnableTtlCallable来修饰传入线程池的RunnableCallable

示例代码:

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();

上面演示了RunnableCallable的处理类似

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 修饰线程池

省去每次RunnableCallable传入线程池时的修饰,这个逻辑可以在线程池中完成。

通过工具类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也就透明支持了CompletableFutureStream。🎉
  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

TimerJDK 1.3的老类,不推荐使用Timer类。

推荐用ScheduledExecutorService
ScheduledThreadPoolExecutor实现更强壮,并且功能更丰富。 如支持配置线程池的大小(Timer只有一个线程);TimerRunnable中抛出异常会中止定时执行。更多说明参见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上。

TTLv2.6.0开始,加载TTL Agent时会自动设置TTL Jarboot class path上。
注意:不能修改从Maven库下载的TTL Jar文件名(形如transmittable-thread-local-2.x.x.jar)。 如果修改了,则需要自己手动通过-Xbootclasspath JVM参数来显式配置(就像TTL之前的版本的做法一样)。

自动设置TTL Jarboot 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

如果修改了下载的TTLJar的文件名(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.4</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_511.7.0_45可以运行。
    1.7.0_45还是有JavaLaunchHelper的出错信息,但不影响运行。

🗿 更多文档

📚 相关资料

Jdk Core Classes

展开阅读全文

代码

的 Gitee 指数为
超过 的项目

评论 (0)

加载中
更多评论
暂无内容
2018/03/21 22:50

Multi-thread

Thread Java线程一共有七个状态,分别是新建,可运行,运行中,睡眠,阻塞,等待,死亡。 线程间通信: wait()/notify():必须在synchronized同步块中使用,wait()是在线程获得了对象的锁后主动释放锁同时线程进入wait状态,其他线程获得了释放的对象锁后,可调notify()唤醒wait的线程,notify()调用后并不立即生效,在同步块执行完后才生效。若有多个线程wait,notify只会唤醒一个线程,由jvm决定唤醒哪个。被notify唤醒的线程...

1
0
发表了博客
2018/06/11 14:15

Multi account chang login with multi -thread

void worker_DoWork(object sender, DoWorkEventArgs e) { isBussy = true; if (Common.isChangingAccount) { rt = new ResultInfo() { code = 3, isSucc = false, msg = "now system change account" }; return; } if (isTest) { rt = new ResultInfo() { code=1, isSuc...

0
0
发表了博客
2016/05/14 16:59

Multi thread: std::promise

前面我们了解到可以使用std::shared_future/std::shared_future在多个其他线程中处理另外一个线程的函数的返回结果. 那么疑问来了,我们要怎样在多线程之间传递数据呢? demo1 :std::promise在线程之间传递参数/处理异常: #include <iostream> #include <thread> #include <chrono> #include <future> #include <utility> #include <exception> #include <stdexcept> #include <string> void doSomething(std::promise<std::st...

0
1
发表了博客
2016/05/18 16:48

C++11: Multi-Thread思考.

使用多线程几乎总是伴随着"数据的并发访问",多个线程之间毫无干系的并行运行是很罕见的。线程有可能提供数据给其他线程处理,或者是备妥必要条件(比如shared state)用以供其它线程使用,或者是供其它线程ublock. 首先我们要明确的是自C++11起:不同的对象拥有各自的内存区. Concurrent Data Access为什么会造成问题? 1,Unsynchronized data access(未同步化的数据访问):并行运行的两个线程读写同一个内存中的数据,不知道哪个语句...

0
1
发表了博客
2016/05/13 15:44

Multi thread: std::shared_future(2)

在之前我们了解到std::future,但是通过class std::future获得的结果只能get()一次,第二次调用通常会抛出一个std::future_error。 但是当多个其他线程想处理另外一个线程的结果的时候,也就是需要多次get(),基于这个目的C++ standard library提供了 std::shared_future,于是我们就可以多次调用get(),拿到相同的结果,或者抛出(throw)同一个异常. std::shared_future相对于std::future来说只是少了一个share()操作.别的操作基本上...

0
0
发表了博客
2016/05/10 21:10

Multi thread: std::async()和std::future(1)

对于初学者而言,“以多线程运行程序”的最佳起点就是C++标准库中的 std::async() 和 class std::future提供的高级接口. (1),std::async()提供一个接口,让一段机能或者说一个callable object function(可调用函数对象)若是可能的话在后台运行成为一个独立的线程. (2),std::future允许你等待线程结束并获取其结果(一个返回值,也有可能是一个异常). #include <iostream> #include <future> #include <chrono> #include <random...

0
3
发表了博客
2018/04/06 16:29

[Node.js] 06 - Multi-thread and process module

课前阅读:关于Node.js后端架构的一点后知后觉 书推荐:<Node.js design patterns> 衍生问题: 微服务的必要性,Flux架构 容错性和拓展性 一、立体拓展 假设现在需要你用NodeJS搭建一个http服务,我猜测你会借助express框架用不到10行的代码完成这项工作。 但容错性和拓展性才是正常运行的基本保障,至少保证了你的服务是可用的,永远是可用的。 X轴方向:纯粹的对服务实例进行拓展,例如为了响应更多的请求   y轴方向:为服务...

0
0
没有更多内容
加载失败,请刷新页面
点击加载更多
加载中
下一页
暂无内容
0 评论
48 收藏
分享
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部