功能

在使用线程池等会缓存线程的组件情况下,提供ThreadLocal值的传递功能。

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

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

欢迎

需求场景

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

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

  1. 分布式跟踪系统

  2. 应用容器或上层框架跨应用代码给下层SDK传递信息

  3. 日志收集记录系统上下文

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

User Guide

使用类TransmittableThreadLocal来保存上下文,并跨线程池传递。

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

InheritableThreadLocal,添加了protected方法copy,用于定制 任务提交给线程池时的上下文传递到 任务执行时时的拷贝行为,缺省是传递的是引用。

具体使用方式见下面的说明。

1. 简单使用

父线程给子线程传递值。

示例代码:

// 在父线程中设置 
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();
parent.set("value-set-in-parent"); // ===================================================== 
// 在子线程中可以读取, 值是"value-set-in-parent" 
String value = parent.get();

这是其实是InheritableThreadLocal的功能,应该使用InheritableThreadLocal来完成。

但对于使用了异步执行(往往使用线程池完成)的情况,线程由线程池创建好,并且线程是缓存起来反复使用的。

这时父子线程关系的上下文传递已经没有意义,应用中要做上下文传递,实际上是在把 任务提交给线程池时的上下文传递到任务执行时。 解决方法参见下面的这几种用法。

2. 保证线程池中传递值

2.1 修饰Runnable和Callable

使用com.alibaba.ttl.TtlRunnablecom.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();

整个过程的完整时序图

2.2 修饰线程池

省去每次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();

2.3 使用Java Agent来修饰JDK线程池实现类

这种方式,实现线程池的传递是透明的,代码中没有修饰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。

Java Agent的使用方式在什么情况下TTL会失效

由于Runnable和Callable的修饰代码,是在线程池类中插入的。下面的情况会让插入的代码被绕过,传递会失效。

  • 用户代码中继承java.util.concurrent.ThreadPoolExecutor和java.util.concurrent.ScheduledThreadPoolExecutor, 覆盖了execute、submit、schedule等提交任务的方法,并且没有调用父类的方法。
    修改线程池类的实现,execute、submit、schedule等提交任务的方法禁止这些被覆盖,可以规避这个问题。

  • 目前,没有修饰java.util.Timer类,使用Timer时,TTL会有问题。

Java API Docs

当前版本的Java API文档地址: http://alibaba.github.io/transmittable-thread-local/apidocs/

Maven依赖

示例:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
    <version>2.0.0</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的出错信息,但不影响运行。

更多文档

阿里开源支持缓存线程池的ThreadLocal Transmittable ThreadLocal(TTL)的更多相关文章

  1. (CSDN 迁移) JAVA多线程实现-可回收缓存线程池(newCachedThreadPool)

    在前两篇博客中介绍了单线程化线程池(newSingleThreadExecutor).可控最大并发数线程池(newFixedThreadPool).下面介绍的是第三种newCachedThreadPo ...

  2. 0041 Java学习笔记-多线程-线程池、ForkJoinPool、ThreadLocal

    什么是线程池 创建线程,因为涉及到跟操作系统交互,比较耗费资源.如果要创建大量的线程,而每个线程的生存期又很短,这时候就应该使用线程池了,就像数据库的连接池一样,预先开启一定数量的线程,有任务了就将任 ...

  3. 8、java5线程池之动态缓存线程池newCachedThreadPool

    JDK文档描述 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们.对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能.调用 execute 将重用以前构造的线程 ...

  4. 常见线程池之 newCacheThreadPool 缓存线程池 简单使用

    package com.aaa.threaddemo; import java.util.concurrent.BlockingQueue; import java.util.concurrent.E ...

  5. cachedThreadPool缓存线程池

    package com.loan.modules.common.util; import java.util.concurrent.BlockingQueue; import java.util.co ...

  6. 阿里开源的缓存框架JetCache

    之前一直在用Spring Cache进行接口数据的缓存,主要是Spring Cache在对具体key缓存失效时间的设置不是很方法,还要自己去扩展,无意中发现了阿里的JetCache.大部分的需求都能满 ...

  7. 【转】线程池体系介绍及从阿里Java开发手册学习线程池的正确创建方法

    jdk1.7中java.util.concurrent.Executor线程池体系介绍 java.util.concurrent.Executor : 负责线程的使用与调度的根接口  |–Execut ...

  8. 阿里P7告诉你什么是java并发包、线程池、锁

    并发包 java.util.concurrent从jdk1.5开始新加入的一个包,致力于解决并发编程的线程安全问题,使用户能够更为快捷方便的编写多线程情况下的并发程序. 同步容器 同步容器只有包括Ve ...

  9. (CSDN 迁移) JAVA多线程实现-支持定时与周期性任务的线程池(newScheduledThreadPool)

    前几篇文章中分别介绍了 单线程化线程池(newSingleThreadExecutor) 可控最大并发数线程池(newFixedThreadPool) 可回收缓存线程池(newCachedThread ...

随机推荐

  1. Pytest命令行执行测试

    Pytest命令行执行测试 from collections import namedtuple Task = namedtuple('Task', ['summary','owner','done' ...

  2. TCP的半连接

    T C P提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力.这就是所谓下载的半关闭.正如我们早些时候提到的只有很少的应用程序使用它.为了使用这个特性,编程接口必须为应用程序提供一种方式来说 ...

  3. linux实操_shell系统函数

    basename函数: 功能:返回完整路径最后/的后面部分,常用于获取文件名. 基本语法: basename 路径 后缀 不加后缀:运行后 加后缀:运行后 dirname函数: 功能:返回完整路径最后 ...

  4. 前端知识体系:JavaScript基础-作用域和闭包-JavaScript的作用域和作用域链

    JavaScript的作用域和作用域链 作用域: 变量的作用域无非两种:全局作用域和局部作用域 全局作用域: 最外层函数定义的变量拥有全局作用域.即对任何内部函数来说都是可以访问的. <scri ...

  5. ansible模块补充

    1.fetch模块, 将远程机器上的文件拉取到本地,以ip或者主机名生成目录,并保留原来的目录结构,与copy模块的功能相反. 主要参数 : dest  --  目标地址 src -- 源地址 例子 ...

  6. 【JUC系列第三篇】-CAS算法详解

    作者 : 毕来生 微信: 878799579 1.CAS是什么? CAS是英文单词(Compare-And-Swap)的缩写,中文意思是:比较并替换.CAS需要有3个操作数:内存地址V,旧的预期值A, ...

  7. 【模板】A*B Problem升级版(FFT快速傅里叶)

    题目描述 给出两个 $n$ 位10进制数x和y,求x*y(详见 洛谷P1919) 分析 假设已经学会了FFT/NTT. 高精度乘法只是多项式乘法的特殊情况,相当于$x=10$ 时. 例如n=3,求12 ...

  8. C/C++应用--Window下获取硬件信息(CPU, 硬盘,网卡等)

    一.头文件如下: #include <Windows.h> #include <string> #include <iostream> #include <w ...

  9. Codeforces 1220 E Tourism

    题面 可以发现一个边双必然是可以随意走的,所以我们就把原图求割边然后把边双缩成一个点,然后就是一个树上dp了. #include<bits/stdc++.h> #define ll lon ...

  10. ICP、MRR、BKA等特性

    一.Index Condition Pushdown(ICP) Index Condition Pushdown (ICP)是 mysql 使用索引从表中检索行数据的一种优化方式,从mysql5.6开 ...