当前位置: 首页 > 软件库 > 程序开发 > 缓存系统 >

Transmittable ThreadLocal(TTL)

支持缓存线程池的 ThreadLocal
授权协议 Apache
开发语言 Java
所属分类 程序开发、 缓存系统
软件类型 开源软件
地区 不详
投 递 者 裴俊能
操作系统 跨平台
开源组织 阿里巴巴
适用人群 未知
 软件概览

在使用线程池等会池化复用线程的执行组件情况下,提供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

  • 需求场景 ThreadLocal的需求场景即TransmittableThreadLocal的潜在需求场景,如果你的业务需要『在使用线程池等会池化复用线程的执行组件情况下传递ThreadLocal值』则是TransmittableThreadLocal目标场景。 下面是几个典型场景例子。 分布式跟踪系统 或 全链路压测(即链路打标) 日志收集记录系统上下文 Session级Cache 应用容器或上

  • 说明 首先TTL(transmittable-thread-local)是阿里开源用于解决线程池ThreadLocal的框架,详细介绍可以到官网查看。github开源地址 我写这篇文章主要是为了介绍一个冷门的使用场景————如何兼容二方三方包已有的ThreadLocal的场景。 案例: 我有一个二方包,用户管理用户信息代码如下,如果单线程模式,完全没有任何问题,我将用户封装到ThreadLocal

  • 本文分为两部分,上部分为使用方式,下部分为解析 1.使用ThreadLocal // 大家都知道在Tl(ThreadLocal)中 数据可以在Thread中共享 // 那么以下场景会存在问题 ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); threadLocal.set(1); new Thread(new Runnable() {

  • 转自:https://github.com/alibaba/transmittable-thread-local/issues/123 应用场景的文章 Java 多线程上下文传递在复杂场景下的实践 by vivo互联网技术(海外商城租户区分) 2021-02-01 Spring Security OAuth2.0认证授权五:用户信息扩展到jwt 2021-01-14 再谈Token认证,如何快速方

  • 什么是ThreadLocal ThreadLocal是Therad的局部变量的维护类,在Java中是作为一个特殊的变量存储在。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 因为每个Thread内有自己的实例副本,且该副本只能由当前Thread使用,也就不存在多线程间

  • 使用ThreadLocal在线程间传递参数 使用场景 用户参数透传,如租户ID,tid,userID,Token等。这些参数与具体业务无关,但是又是必须的(如:租户ID,在数据落库时需要传入,但是在其他业务代码中基本用不到)如果作为方法的参数一层一层向下传递,必然造成代码的冗余和扩展性查等影响。 整片文章就以租户ID为例子说明吧。 1.使用ThreadLocal结合AOP 大致思路:在请求接口时,

 相关资料
  • 本文向大家介绍固定线程池和缓存线程池之间的区别。,包括了固定线程池和缓存线程池之间的区别。的使用技巧和注意事项,需要的朋友参考一下 执行器框架是使用线程池概念设计的。线程池是重用已经创建的线程的方法,而不是每次执行当前任务都创建一个新线程的方法。 Executors类提供了一种工厂方法来创建线程池。ThreadPoolExecutor类是从许多Executors工厂方法返回的执行器的基本实现。 序

  • 一个好的HTTP缓存策略可以极大地提高一个web应用的性能及客户端的体验。谈到HTTP缓存,它主要是与HTTP的响应头'Cache-Control'相关,其次另外的一些响应头比如'Last-Modified'和'ETag'等也会起一定的作用。 HTTP的响应头'Cache-Control'主要帮助私有缓存(比如浏览器端缓存)和公共缓存(比如代理端缓存)了解它们应该如果缓存HTTP响应,以便后用。

  • 问题内容: 我刚刚开始研究Java的类和方法。根据API,生成的线程池将现有对象重用于新任务。 我对此感到有些困惑,因为我无法在API中找到任何方法来设置现有对象的行为。 例如,您可以创建一个 新的 从一个对象,这使得调用的方法。但是,API中没有使用a 作为参数的setter方法。 我将不胜感激任何指针。 问题答案: 执行人员在后台为您完成所有工作。是的,它仅使用现有的线程API。 下面的链接提

  • 我刚刚开始研究Java的类和方法。根据API,生成的线程池重用现有的对象来执行新任务。 我有点困惑这是如何实现的,因为我在API中找不到任何方法可以设置现有对象的行为。 例如,可以从对象创建新的,这使得调用的方法。但是,API中没有将作为参数的setter方法。 我会很感激你的指点。

  • 我在应用程序中尝试将Spring缓存与ehcache结合使用时遇到了一个问题。由于无法详细说明的原因,我的应用程序使用BeanFactory图而不是ApplicationContext。正如Spring文档中所述,只要我们手动注册BeanPostProcessor,这种方法就可以很好地工作。 我们现在正在为应用程序添加缓存。当我们使用最简单的注释配置时,它可以工作。 //这很有效 我们将其配置为为

  • 一个轻量级的缓存实现,目前已支持 Redis Memcache Memcached File 四种储存模式 仓库地址: Github 安装 composer require easyswoole/cache 注意: 请确保框架已经引入了 composer 的 autoload.php 文件,否则报类不存在的错误 快速入门 如果不做任何设置,默认使用File驱动,开箱即用 use easySwool

  • 我对spring boot中的缓存技术很陌生,我在我的项目中使用了缓存。现在我有一个新的需求,需要设置缓存TTL,并且TTL必须在密钥基础上扩展。 E、 g.如果我将缓存过期时间设置为5分钟,则在缓存过期之前,必须检查传入密钥是否存在于缓存中。如果传入密钥存在于缓存中,则TTL必须延长5分钟,否则应清除或收回缓存,并创建新的缓存。 我希望我清楚我的要求,请提供我的解决方案与完美的例子,适合我的要求