Alibaba-技术专区-开源项目之TransmittableThreadLocal
1.简介
TransmittableThreadLocal 是Alibaba开源的、用于解决 “在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal 扩展。若希望 TransmittableThreadLocal 在线程池与主线程间传递,需配合 TtlRunnable 和 TtlCallable 使用
2、使用场景
分布式跟踪系统
应用容器或上层框架跨应用代码给下层SDK传递信息
日志收集记录系统上下文
3、简单分析使用
JDK
的InheritableThreadLocal
类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal
值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时的ThreadLocal
值传递到 任务执行时。下面分析下
InheritableThreadLoc
InheritableThreadLocal类重写了ThreadLocal的3个函数:
/**
* 该函数在父线程创建子线程,向子线程复制InheritableThreadLocal变量时使用
*/
protected T childValue(T parentValue) {
return parentValue;
}/**
* 由于重写了getMap,操作InheritableThreadLocal时,
* 将只影响Thread类中的inheritableThreadLocals变量,
* 与threadLocals变量不再有关系
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}/**
* 类似于getMap,操作InheritableThreadLocal时,
* 将只影响Thread类中的inheritableThreadLocals变量,
* 与threadLocals变量不再有关系
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
注意:重写了getMap()和createMap()两个函数,说到InheritableThreadLocal,还要从Thread类说起:
public class Thread implements Runnable {
......(其他源码)
/*
* 当前线程的ThreadLocalMap,主要存储该线程自身的ThreadLocal
*/
ThreadLocal.ThreadLocalMap threadLocals = null;/*
* InheritableThreadLocal,自父线程继承而来的ThreadLocalMap,
* 主要用于父子线程间ThreadLocal变量的传递
* 本文主要讨论的就是这个ThreadLocalMap
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
......(其他源码)
}
Thread类中包含 threadLocals 和 inheritableThreadLocals 两个变量,其中 inheritableThreadLocals 即主要存储可自动向子线程中传递的ThreadLocal.ThreadLocalMap。
Thread thread = new Thread();
**
* Allocates a new {@code Thread} object. This constructor has the same
* effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
* {@code (null, null, gname)}, where {@code gname} is a newly generated
* name. Automatically generated names are of the form
* {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
*/
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}Thread初始化
/**
* 默认情况下,设置inheritThreadLocals可传递
*/
private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
init(g, target, name, stackSize, null, true);
}/**
* 初始化一个线程.
* 此函数有两处调用,
* 1、上面的 init(),不传AccessControlContext,inheritThreadLocals=true
* 2、传递AccessControlContext,inheritThreadLocals=false
*/
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {
......(其他代码)if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);......(其他代码)
}
可以看到,采用默认方式产生子线程时,inheritThreadLocals=true;若此时父线程inheritableThreadLocals不为空,则将父线程inheritableThreadLocals传递至子线程。让我们继续追踪createInheritedMap。
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}/**
* 构建一个包含所有parentMap中Inheritable ThreadLocals的ThreadLocalMap
* 该函数只被 createInheritedMap() 调用.
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
// ThreadLocalMap 使用 Entry[] table 存储ThreadLocal
table = new Entry[len];// 逐一复制 parentMap 的记录
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
// 可能会有同学好奇此处为何使用childValue,而不是直接赋值,
// 毕竟childValue内部也是直接将e.value返回;
// 个人理解,主要为了减轻阅读代码的难度
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
从ThreadLocalMap可知,子线程将parentMap中的所有记录逐一复制至自身线程。InheritableThreadLocal主要用于子线程创建时,需要自动继承父线程的ThreadLocal变量,方便必要信息的进一步传递。
接下来提供的
TransmittableThreadLocal
类继承并加强InheritableThreadLocal
类,解决上述的问题。使用类
TransmittableThreadLocal
来保存值,并跨线程池传递。TransmittableThreadLocal
继承InheritableThreadLocal
,使用方式也类似。相比
InheritableThreadLocal
,添加了
protected
方法copy
用于定制任务提交给线程池时的ThreadLocal
值传递到 任务执行时 的拷贝行为,缺省传递的是引用。protected
方法beforeExecute
/afterExecute
执行任务(Runnable
/Callable
)的前/后的生命周期回调,缺省是空操作。
1. 简单使用
父线程给子线程传递值。
示例代码:
// 在父线程中设置
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();parent.set("value-set-in-parent");// 在子线程中可以读取,值是"value-set-in-parent"
String value = parent.get();
这是其实是InheritableThreadLocal
的功能,应该使用InheritableThreadLocal
来完成。
但对于使用线程池等会池化复用线程的组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal
值传递已经没有意义,应用需要的实际上是把任务提交给线程池时的ThreadLocal
值传递到 任务执行时。
解决方法参见下面的这几种用法。
- 保证线程池中传递值
- 修饰Runnable和Callable
- 使用TtlRunnable和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
getTtlScheduledExecutorService
:修饰接口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();
目前TTL Agent
中,修饰了JDK
中的线程池实现如下:
java.util.concurrent.ThreadPoolExecutor
和java.util.concurrent.ScheduledThreadPoolExecutor
修饰实现代码在TtlExecutorTransformlet.java
java.util.concurrent.ForkJoinTask
(对应的线程池组件是java.util.concurrent.ForkJoinPool
)修饰实现代码在TtlForkJoinTransformlet.java
java.util.TimerTask
的子类(对应的线程池组件是java.util.Timer
)修饰实现代码在TtlTimerTaskTransformlet.java
注意:缺省没有开启TimerTask
的修饰,使用Agent
参数ttl.agent.enable.timer.task
开启:-javaagent:path/to/transmittable-thread-local-2.x.x.jar=ttl.agent.enable.timer.task:true
。更多关于TTL Agent
参数的配置说明详见TtlAgent.java
的JavaDoc。
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
的代码,所以TTL
的Jar
需要加到boot class path
上。
TTL
从v2.6.0
开始,加载TTL Agent
会自动把自己的Jar
设置到boot class path
上。注意:不能修改从
Maven
库下载的TTL
的Jar
的文件名(形如transmittable-thread-local-2.x.x.jar
)。 如果修改了,则需要自己手动通过-Xbootclasspath JVM
参数来显式配置(就像TTL
之前的版本的做法一样)。实现是通过指定
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
。如果修改了下载的
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.ttl.threadpool.agent.demo.AgentDemo或是
java -javaagent:path/to/ttl-foo-name-changed.jar \
-Xbootclasspath/a:path/to/ttl-foo-name-changed.jar \
-cp classes \
com.alibaba.ttl.threadpool.agent.demo.AgentDemo
Maven依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.10.2</version>
</dependency>
Alibaba-技术专区-开源项目之TransmittableThreadLocal的更多相关文章
- 【Spring Cloud & Alibaba全栈开源项目实战】:SpringBoot整合ELK实现分布式登录日志收集和统计
一. 前言 其实早前就想计划出这篇文章,但是最近主要精力在完善微服务.系统权限设计.微信小程序和管理前端的功能,不过好在有群里小伙伴的一起帮忙反馈问题,基础版的功能已经差不多,也在此谢过,希望今后大家 ...
- GitHub 被指审查内容,著名“换脸”开源项目 deepfake 遭限制访问
开发四年只会写业务代码,分布式高并发都不会还做程序员? >>> 昨天 Hacker News 上一条关于 deepfake 开源项目的帖子(https://news.ycombi ...
- 阿里巴巴开源项目:分布式数据库同步系统otter(解决中美异地机房) - agapple - ITeye技术网站
阿里巴巴开源项目:分布式数据库同步系统otter(解决中美异地机房) - agapple - ITeye技术网站 阿里巴巴开源项目:分布式数据库同步系统otter(解决中美异地机房)
- 前端技术-svg简介与snap.svg.js开源项目的使用
前言-为什么学习snap.svg.js 前阵子webAPP的技术群里有人感觉到svg+animate的形式感觉很炫,矢量图任意放大且不需要下载图片,并且在手机端效果流畅. (矢量图与位图最大的区别是, ...
- 以正确的方式开源 Python 项目 - 技术翻译 - 开源中国社区
以正确的方式开源 Python 项目 - 技术翻译 - 开源中国社区 以正确的方式开源 Python 项目 英文原文:Open Sourcing a Python Project the Right ...
- 开源项目几点心得,Java架构必会几大技术点
关于学习架构,必须会的几点技术 1. java反射技术 2. xml文件处理 3. properties属性文件处理 4. 线程安全机制 5. annocation注解 ...
- Spring 社区的唯一一个国产开源项目 - Spring Cloud Alibaba 毕业了
阿里妹导读:一年多前,Java 界最近发生了一件大事,阿里开源 Spring Cloud Alibaba,并推出首个预览版.Spring Cloud 本身是一套微服务规范,并不是一个拿来即可用的框架, ...
- 【Android 应用开发】GitHub 优秀的 Android 开源项目
原文地址为http://www.trinea.cn/android/android-open-source-projects-view/,作者Trinea 主要介绍那些不错个性化的View,包括Lis ...
- 【分享】2017 开源中国新增开源项目排行榜 TOP 100
2017 年开源中国社区新增开源项目排行榜 TOP 100 新鲜出炉! 这份榜单根据 2017 年开源中国社区新收录的开源项目的关注度和活跃度整理而来,这份最受关注的 100 款开源项目榜单在一定程度 ...
随机推荐
- ==和equal的区别
1.“==”运算符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用==操作符. 如果一个变量指向的数据是对 ...
- C++中的抽象类和接口
1,在 C++ 语言中,并不直接支持面向对象中的抽象类和接口概念,但是 C++ 语言 却可以间接实现这些概念: 2,什么是抽象类: 1,面向对象中的抽象(其实就是分类)概念: 1,在进行面向对象分析时 ...
- 44-python基础-python3-字符串-常用字符串方法(二)-isalpha()-isalnum()-isdigit()-isspace()-istitle()
3-isX 字符串方法 序号 方法 条件 返回结果1 返回结果2 1 isalpha() 如果字符串只包含字母,并且非空; True False 2 isalnum() 如果字符串只包含字母和数字 ...
- BUUCTF CRYPTO部分题目wp
对密码学了解不多,做一下熟悉熟悉 1,看我回旋踢 给的密文synt{5pq1004q-86n5-46q8-o720-oro5on0417r1} 简单的凯撒密码,用http://www.zjslove. ...
- go中浮点型用法总结
示例 // 浮点型的用法 package main import ( "fmt" "unsafe" ) func main() { // 如果浮点数声明时未指定 ...
- 初始 vue
1.js,jQuery编程范式:命令式编程 vue编程范式:声明式编程 v-for 遍历数组内容 v-on: click 监听点击事件,语法糖 @click el: 类型:string | H ...
- Linux运维常用脚本整理
.查找当前目录下占用为0字节的文件并删除 find ./ -type f -size -exec rm -rf {}\; #此命令不要用于对根目录0字节文件的操作 .将系统进程按内存占用大小排列 ...
- Java集合框架是什么?说出一些集合框架的优点?
每种编程语言中都有集合,最初的Java版本包含几种集合类:Vector.Stack.HashTable和Array. 随着集合的广泛使用,Java1.2提出了囊括所有集合接口.实现和算法的集合框架.在 ...
- SCP-bzoj-1090
项目编号:bzoj-1090 项目等级:Safe 项目描述: 戳这里 特殊收容措施: 区间DP.设计状态f[i][j]表示压缩从第i位到第j位的字符串所需的最小长度.转移方式有三种: •初始化:j-i ...
- 调用windows的复制文件对话框
function CopyFileDir(sDirName: String; sToDirName: String): Boolean; var fo: TSHFILEOPSTRUCT; begin ...