从TL、ITL到TT
1、概述
ThreadLocal(TL)是Java中一种线程局部变量实现机制,他为每个线程提供一个单独的变量副本,保证多线程场景下,变量的线程安全。经常用于代替参数的显式传递。
InheritableThreadLocal(ITL)是JDK提供的TL增强版,而TransmittableThreadLocal(TTL)是阿里开源的ITL增强版
这些ThreadLocal在不同场景下有不同用途,我们来分析一下:
2、ThreadLocal
ThreadLocal主要的方法有四个:initialValue、set、get、remove
2.1、初始化——initialValule
当线程首次访问该ThreadLocal时(ThreadLocal.get()),会进行初始化赋值。我们常用两种方法初始化ThreadLocal
2.1.1、重写initialValue
ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "";
}
};
2.1.2、调用ThreadLocal.withInitial
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "");
他会创建一个SuppliedThreadLocal内部类
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
该类重写了initialValue方法
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
//当该线程首次访问ThreadLocal时,会间接调用lambda表达式初始化
return supplier.get();
}
}
️ITL并没有重新实现withInitial,如果使用withInitial则会创建STL,失去自己增强的特性
2.2、赋值——set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
这里出现了一个关键属性ThreadLocalMap,类定义在ThreadLocal中,是Thread的成员变量
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocalMap内部还有一个内部类Entry,是存值的地方
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
//ThreadLocal的引用是“key”
super(k);
//线程局部变量是value
value = v;
}
}
//Entry数组
//value具体放在哪个index下,是由ThreadLocal的hashCode算出来的
private Entry[] table;
}
2.3、取值——get
public T get() {
Thread t = Thread.currentThread();
//1、获取线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//2、根据ThreadLocal的hashCode,获取对应Entry下的value
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//3、如果没有赋过值,则初始化
return setInitialValue();
}
2.4、清空——remove
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//会将对应Entry、包括他的key、value手动置null
m.remove(this);
}
3、InheritableThreadLocal
3.1、TL在父子线程场景下存在的问题
我们先来看一个例子
public static void main(String[] args) throws InterruptedException {
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "A");
threadLocal.set("B");
Thread thread = new Thread(() -> {
System.out.println("子线程ThreadLocal:" + threadLocal.get());
}, "子线程");
thread.start();
thread.join();
}
打印结果如下,可见子线程的ThreadLocal是初始值,并没有使用父线程修改后的值:
子线程ThreadLocal:A
线程的ThreadLocalMap是首次访问时创建的,所以子线程使用ThreadLocal的时候,会初始化一个新的ThreadLocal,线程局部变量为默认值
️所以,TL不具有遗传性
3.2、ITL的解决方案
为了解决TL子线程遗传性的问题,JDK引入了ITL
他继承ThreadLocal,重写了childValue、getMap、createMap三个方法
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
这里出现了inheritableThreadLocals,他存储的就是从父线程拷贝过来的ThreadLocal,这个值是在父线程首次修改ThreadLocal的时候赋值的,然后在子线程创建时拷贝过来的
//父线程部分:
public void set(T value) {
Thread t = Thread.currentThread();
//该方法被ITL重写,访问inheritableThreadLocals为null
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//该方法同样被ITL重写,创建一个ThreadLocalMap赋值给inheritableThreadLocals
createMap(t, value);
}
//子线程部分:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//省略一些代码...
//获取当前线程(父线程、也就是创建子线程的线程)
Thread parent = currentThread();
//1、允许ThreadLocal遗传(这个默认为true)
//2、inheritableThreadLocals不为空,因为父线程调用set了
//父线程不调用set,那ThreadLocal就是初始值,那直接初始化就好了,也不用进该分支
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
//createInheritedMap使用该构造函数,根据父线程的inheritableThreadLocals进行深拷贝
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
//深拷贝父线程ThreadLocalMap
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被ITL重写,返回父线程ThreadLocal的值
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++;
}
}
}
}
使用ITL的效果
public static void main(String[] args) throws InterruptedException {
ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>() {
@Override
protected String initialValue() {
return "A";
}
};
threadLocal.set("B");
Thread thread = new Thread(() -> {
System.out.println("子线程ThreadLocal:" + threadLocal.get());
}, "子线程");
thread.start();
thread.join();
}
打印结果如下,子线程拷贝了父线程ThreadLocal:
子线程ThreadLocal:B
总结一下,ITL解决父子线程遗传性的核心思路是,将可遗传的ThreadLocal放在父线程新的ThreadLocalMap中,在子线程首次使用时进行拷贝
4.、TransmittableThreadLocal
4.1、ITL在线程复用场景下存在的问题
我们再从一个简单的例子说起
public static void main(String[] args) throws InterruptedException, ExecutionException {
ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>() {
@Override
protected String initialValue() {
return "A";
}
};
threadLocal.set("B");
ExecutorService executorService = Executors.newFixedThreadPool(1);
//1、子线程第一次获取ThreadLocal
executorService.submit(() -> System.out.println("子线程ThreadLocal:"+threadLocal.get())).get();
Thread.sleep(1000);
//2、父线程修改ThreadLocal
threadLocal.set("C");
System.out.println("父线程修改ThreadLocal为"+threadLocal.get());
//3、子线程第二次获取ThreadLocal
executorService.submit(() -> System.out.println("子线程ThreadLocal:"+threadLocal.get())).get();
}
打印结果如下,子线程在第二次打印时,并没有拷贝父线程的ThreadLocal,使用的还是首次拷贝的值:
子线程ThreadLocal:B
父线程修改ThreadLocal为C
子线程ThreadLocal:B
️可复用的子线程不会感知父线程ThreadLocal的变化
4.2、TTL的解决方案
4.2.1、TTL的使用
TTL在ITL上做了稍微复杂的封装,我们从使用开始了解
引入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>latest</version>
</dependency>
在使用TTL时,线程需要经过TTL封装,线程池同理
public static void main(String[] args) throws InterruptedException, ExecutionException {
ThreadLocal<String> threadLocal = new TransmittableThreadLocal<String>() {
@Override
protected String initialValue() {
return "A";
}
};
threadLocal.set("B");
ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));
executorService.submit(() -> System.out.println("子线程ThreadLocal:" + threadLocal.get())).get();
Thread.sleep(1000);
threadLocal.set("C");
System.out.println("父线程修改ThreadLocal为" + threadLocal.get());
executorService.submit(() -> System.out.println("子线程ThreadLocal:" + threadLocal.get())).get();
Thread.sleep(1000);
executorService.submit(() -> {
threadLocal.set("D");
System.out.println("子线程修改ThreadLocal为" + threadLocal.get());
});
Thread.sleep(1000);
executorService.submit(() -> System.out.println("子线程ThreadLocal:" + threadLocal.get()));
Thread.sleep(1000);
}
打印结果如下,子线程每次都会获取父线程的ThreadLocal
子线程ThreadLocal:B
父线程修改ThreadLocal为C
子线程ThreadLocal:C
子线程修改ThreadLocal为D
子线程ThreadLocal:C
从使用上看,TTL要求将任务封装,那我们就从ThreadLocal和ExecutorService两部分入手
4.2.2、TTL对ThreadLocal的封装
下面是TTL的取值和赋值逻辑,都涉及一个关键方法addThisToHolder,对应的属性holder会在线程池执行任务时用到
//TransmittableThreadLocal.addThisToHolder()
private void addThisToHolder() {
//InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder
if (!holder.get().containsKey(this)) {
//holder是静态变量,他会把TTL存到当前线程的map中
//value是null,他其实是把Map当Set用
//主线程赋值时,会获取主线程的holderMap,然后把TTL存进去
holder.get().put((TransmittableThreadLocal<Object>) this, null);
}
}
@Override
public final void set(T value) {
if (!disableIgnoreNullValueSemantics && null == value) {
remove();
} else {
super.set(value);
//当主线程赋值时,会将自己的TTL放到自己的map中
addThisToHolder();
}
}
@Override
public final T get() {
T value = super.get();
if (disableIgnoreNullValueSemantics || null != value)
addThisToHolder();
return value;
}
4.2.3、TTL对任务的封装
//我们通过TtlExecutors.getTtlExecutorService()对线程池进行封装
public static ExecutorService getTtlExecutorService(@Nullable ExecutorService executorService) {
if (TtlAgent.isTtlAgentLoaded() || executorService == null || executorService instanceof TtlEnhanced) {
return executorService;
}
//入参是线程池,通过包装类代理线程池的操作
return new ExecutorServiceTtlWrapper(executorService);
}
//ExecutorServiceTtlWrapper.submit()
public Future<?> submit(@NonNull Runnable task) {
//将提交的任务进行封装
return executorService.submit(TtlRunnable.get(task));
}
4.2.3.1、任务构建
TtlRunnable构造方法
这里都是主线程在操作,因为任务是主线程提交的
private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
this.capturedRef = new AtomicReference<Object>(capture());
this.runnable = runnable;
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
这里有一个关键属性capturedRef,他是一个原子引用,存了TTL
//TrasmitableThreadLocal.Transmitter
public static Object capture() {
//获取ttl的值构建快照
return new Snapshot(captureTtlValues(), captureThreadLocalValues());
}
private static HashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>();
for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
//将主线程TTL的值存到当前任务中
ttl2Value.put(threadLocal, threadLocal.copyValue());
}
return ttl2Value;
}
4.2.3.2、任务执行
任务执行的代码如下,在任务执行前回放ThreadLocal,在任务执行后恢复ThreadLocal:
这里都是子线程在操作,因为任务都是子线程执行的
@Override
public void run() {
Object captured = capturedRef.get();
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
//1、备份子线程ThreadLocal
//2、使用主线程提交任务时构建的ThreadLocal副本,将子线程ThreadLocal覆盖
Object backup = replay(captured);
try {
//3、任务执行
runnable.run();
} finally {
//3、使用之前备份的子线程ThreadLocal进行恢复
restore(backup);
}
}
总结一下,TTL让子线程感知父线程变化的核心思路是,主线程在任务提交时构建ThreadLocal副本,在子线程执行任务时供其使用
️提交和执行任务会对TTL进行若干操作,理论上对性能有一点点影响,官方性能测试结论说损耗可忽略
作者:京东物流 刘朝永
来源:京东云开发者 自猿其说
从TL、ITL到TT的更多相关文章
- CF533F Encoding 题解
题目链接CF533F Encoding 提示1: \(\mathcal O(26^2*n)\) 的算法可通过.常用的几种字符串匹配算法kmp,AC自动机,哈希都可以解决该问题 (后两者可以优化到 ...
- 【网络流24题】No.21 (最长 k 可重区间集问题 最长不相交路径 最大费用流)
[] 输入文件示例input.txt4 21 76 87 109 13 输出文件示例output.txt15 [分析] 直接co题解好了,写得挺全.. [建模方法] 方法1 按左端点排序所有区间,把每 ...
- Sybase数据库实现等效的mysql中group_concat功能
在MySQL中,如果想实现将分组之后的多个数据合并到一列,可以使用group_concat函数,如下图所示: 但是,在Sybase中没有这样的函数(别问我为什么使用Sybase,因为公司用的Sybas ...
- [ST表/贪心] NOI2010 超级钢琴
[NOI2010]超级钢琴 题目描述 小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙的音乐. 这架超级钢琴可以弹奏出n个音符,编号为1至n.第i ...
- hdu 2141 Can you find it? (二分法)
Can you find it? Time Limit: 10000/3000 MS (Java/Others) Memory Limit: 32768/10000 K (Java/Others ...
- bzoj 2093 [Poi2010]Frog——滑动窗口
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2093 找第k近的可以用一个含k个元素的滑动窗口来实现. 卡空间也还行,但卡时间.不要预处理倍 ...
- SQL Server 存储过程遇到“表 '#TT' 没有标识属性。无法执行 SET 操作”错误
创建临时表,往临时表插入数据的时候报的错误. 一开始提示没有打开主键,后来打开主键就提示上述错误异常. 从网上查找资料没有找到,然后又到群里问各位大牛,一位大牛告诉我是没有设置主键. 我又仔细看看提示 ...
- webapi修改tt模板给字段添加JsonIgnore特性解决转换json循环引用问题
0.问题描述 EF生成的model带有导航属性,则json序列化会报循环引用错误,尝试如下 protected void Application_Start() { GlobalConfigurati ...
- Oracle ITL(Interested Transaction List)理解
ITL(Interested Transaction List) ITL是位于数据块头部的事物槽列表,它是由一系列的ITS(Interested Transaction Slot,事物槽)组成,其初始 ...
- 报错:org.hibernate.AssertionFailure: null id in com.tt.hibernate.entities.News entry (don't flush the Session after an exception occurs)
在使用hibernate创建数据库的表格时,出现了如下报错: 十二月 28, 2016 10:17:02 上午 org.hibernate.tool.hbm2ddl.SchemaExport perf ...
随机推荐
- The first week match's mistake
比赛中的补题中的一些错误 P8506 标题计数(https://www.luogu.com.cn/problem/P8506) 第一眼下去,嗯..贪了,只读到一个'#'后边跟一个空格就+1,结果wa几 ...
- 帝国cms7.5和7.2 搜素模板支持 php和灵动标签以及不起作用
帝国CMS搜索模板不支持动态标签调用,从7.0到7.2再到刚发布的7.5,帝国官方团队始终没解决这个问题,这很不方便,但是帝国的强大可以让我们忽略这个问题,今天老威就把这个bug的解决方法说一下. 第 ...
- ArrayList实现原理和自动扩容
ArrayList在Java集合中的位置, ArrayList原理: transient Object[] elementData; ArrayList通过数组来实现. 默认构造方法会构造一个容量为1 ...
- Python 变量作用域和列表
变量作用域 变量由作用范围限制 分类:按照作用域分类 全局(global):在函数外部定义 局部(local):在函数内部定义 变量的作用范围: 全局变量:在整个全局范围有效 全局碧昂量在局部可以使用 ...
- Selenium 高亮显示操作元素
一.代码 使用 https://www.cnblogs.com/TSmagic/p/16595168.html 此篇代码即可 二.编辑 test_ys_case.py 文件 # coding=utf- ...
- 一天吃透Redis面试八股文
Redis连环40问,绝对够全! Redis是什么? Redis(Remote Dictionary Server)是一个使用 C 语言编写的,高性能非关系型的键值对数据库.与传统数据库不同的是,Re ...
- CS144 计算机网络 Lab4:TCP Connection
前言 经过前面几个实验的铺垫,终于到了将他们组合起来的时候了.Lab4 将实现 TCP Connection 功能,内部含有 TCPReceiver 和 TCPSender,可以与 TCP 连接的另一 ...
- python的docker环境配置
docker环境配置 Docker基本操作指令, 下载安装参考 https://duoke360.com/tutorial/docker/install-docker docker + flask + ...
- DevOps|AGI : 智能时代研发效能平台新引擎(上)
AGI 的出现,给了我们一个新视角去审视我们做过的系统,尤其是研发效能平台.研发效能平台作为一个工具平台,本质就是提高公司整体产研的效率.AGI 的快速进步大家已经有目共睹,本文就是在项目协同,代码管 ...
- 2020-09-29:介绍volatile功能。
福哥答案2020-09-29:#福大大架构师每日一题# 功能如下:1.内存可见.2.禁止指令重排序. 实现如下:1.字节码层面 ACC_VOLATILE 2.JVM层面volatile内存区的读写 都 ...