ThreadLocal父子线程传递实现方案
前言
介绍InheritableThreadLocal之前,假设对 ThreadLocal 已经有了一定的理解,比如基本概念,原理,如果没有,可以参考:ThreadLocal源码分析解密.在讲解之前我们先列举有关ThreadLocal的几个关键点
每一个Thread线程都有属于自己的ThreadLocalMap,里面有一个弱引用的Entry(ThreadLocal,Object),如下
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
从ThreadLocal中get值的时候,首先通过Thread.currentThread得到当前线程,然后拿到这个线程的ThreadLocalMap。再传递当前ThreadLocal对象(结合上一点)。取得Entry中的value值
set值的时候同理,更改的是当先线程的ThreadLocalMap中Entry中key为当前Threadlocal对象的value值
Threadlocal bug?
如果子线程想要拿到父线程的中的ThreadLocal值怎么办呢?比如会有以下的这种代码的实现。由于ThreadLocal的实现机制,在子线程中get时,我们拿到的Thread对象是当前子线程对象,那么他的ThreadLocalMap是null的,所以我们得到的value也是null。
final ThreadLocal threadLocal=new ThreadLocal(){
@Override
protected Object initialValue() {
return "xiezhaodong";
}
};
new Thread(new Runnable() {
@Override
public void run() {
threadLocal.get();//NULL
}
}).start();
InheritableThreadLocal实现
那其实很多时候我们是有子线程获得父线程ThreadLocal的需求的,要如何解决这个问题呢?这就是InheritableThreadLocal这个类所做的事情。先来看下InheritableThreadLocal所做的事情。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
/**
* 重写Threadlocal类中的getMap方法,在原Threadlocal中是返回
*t.theadLocals,而在这么却是返回了inheritableThreadLocals,因为
* Thread类中也有一个要保存父子传递的变量
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
/**
* 同理,在创建ThreadLocalMap的时候不是给t.threadlocal赋值
*而是给inheritableThreadLocals变量赋值
*
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
以上代码大致的意思就是,如果你使用InheritableThreadLocal,那么保存的所有东西都已经不在原来的t.thradLocals里面,而是在一个新的t.inheritableThreadLocals变量中了。下面是Thread类中两个变量的定义
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
Q:InheritableThreadLocal是如何实现在子线程中能拿到当前父线程中的值的呢?
A:一个常见的想法就是把父线程的所有的值都copy到子线程中。
下面来看看在线程new Thread的时候线程都做了些什么?
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
//省略上面部分代码
if (parent.inheritableThreadLocals != null)
//这句话的意思大致不就是,copy父线程parent的map,创建一个新的map赋值给当前线程的inheritableThreadLocals。
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
//ignore
}
而且,在copy过程中是浅拷贝,key和value都是原来的引用地址
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
ThreadLocal key = e.get();
if (key != null) {
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++;
}
}
}
}
恩,到了这里,大致的解释了一下InheritableThreadLocal为什么能解决父子线程传递Threadlcoal值的问题。
在创建InheritableThreadLocal对象的时候赋值给线程的t.inheritableThreadLocals变量
在创建新线程的时候会check父线程中t.inheritableThreadLocals变量是否为null,如果不为null则copy一份ThradLocalMap到子线程的t.inheritableThreadLocals成员变量中去
因为复写了getMap(Thread)和CreateMap()方法,所以get值得时候,就可以在getMap(t)的时候就会从t.inheritableThreadLocals中拿到map对象,从而实现了可以拿到父线程ThreadLocal中的值
so,在最开始的代码示例中,如果把ThreadLocal对象换成InheritableThreadLocal对象,那么get到的字符会是“xiezhaodong”而不是NULL
InheritableThreadLocal还有问题吗?
问题场景
我们在使用线程的时候往往不会只是简单的new Thrad对象,而是使用线程池,当然线程池的好处多多。这里不详解,既然这里提出了问题,那么线程池会给InheritableThreadLocal带来什么问题呢?我们列举一下线程池的特点:
为了减小创建线程的开销,线程池会缓存已经使用过的线程
生命周期统一管理,合理的分配系统资源
对于第一点,如果一个子线程已经使用过,并且会set新的值到ThreadLocal中,那么第二个task提交进来的时候还能获得父线程中的值吗?比如下面这种情况(虽然是线程,用sleep尽量让他们串行的执行):
final InheritableThreadLocal<Span> inheritableThreadLocal = new InheritableThreadLocal<Span>();
inheritableThreadLocal.set(new Span("xiexiexie"));
//输出 xiexiexie
Object o = inheritableThreadLocal.get();
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("========");
inheritableThreadLocal.get();
inheritableThreadLocal.set(new Span("zhangzhangzhang");
inheritableThreadLocal.get();
}
};
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.submit(runnable);
TimeUnit.SECONDS.sleep(1);
executorService.submit(runnable);
TimeUnit.SECONDS.sleep(1);
System.out.println("========");
Span span = inheritableThreadLocal.get();
}
static class Span {
public String name;
public int age;
public Span(String name) {
this.name = name;
}
}
输出的会是
xiexiexie
========
xiexiexie
zhangzhangzhang
========
zhangzhangzhang
zhangzhangzhang
========
xiexiexie
造成这个问题的原因是什么呢,下图大致讲解一下整个过程的变化情况,如图所示,由于B任务提交的时候使用了,A任务的缓存线程,A缓存线程的InheritableThreadLocal中的value已经被更新成了”zhangzhangzhang“。B任务在代码内获得值的时候,直接从t.InheritableThreadLocal中获得值,所以就获得了线程A中心设置的值,而不是父线程中InheritableThreadLocal的值。
so,InheritableThreadLocal还是不能够解决线程池当中获得父线程中ThreadLocal中的值。
造成问题的原因
那么造成这个问题的原因是什么呢?如何让任务之间使用缓存的线程不受影响呢?实际原因是,我们的线程在执行完毕的时候并没有清除ThreadLocal中的值,导致后面的任务重用现在的localMap。
解决方案
如果我们能够,在使用完这个线程的时候清除所有的localMap,在submit新任务的时候在重新重父线程中copy所有的Entry。然后重新给当前线程的t.inhertableThreadLocal赋值。这样就能够解决在线程池中每一个新的任务都能够获得父线程中ThreadLocal中的值而不受其他任务的影响,因为在生命周期完成的时候会自动clear所有的数据。Alibaba的一个库解决了这个问题github:alibaba/transmittable-thread-local
transmittable-thread-local实现原理
如何使用
这个库最简单的方式是这样使用的,通过简单的修饰,使得提交的runable拥有了上一节所述的功能。具体的API文档详见github,这里不再赘述
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();
原理简述
这个方法TtlRunnable.get(task)最终会调用构造方法,返回的是该类本身,也是一个Runable,这样就完成了简单的装饰。最重要的是在run方法这个地方。
public final class TtlRunnable implements Runnable {
private final AtomicReference<Map<TransmittableThreadLocal<?>, Object>> copiedRef;
private final Runnable runnable;
private final boolean releaseTtlValueReferenceAfterRun;
private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
//从父类copy值到本类当中
this.copiedRef = new AtomicReference<Map<TransmittableThreadLocal<?>, Object>>(TransmittableThreadLocal.copy());
this.runnable = runnable;//提交的runable,被修饰对象
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
/**
* wrap method {@link Runnable#run()}.
*/
@Override
public void run() {
Map<TransmittableThreadLocal<?>, Object> copied = copiedRef.get();
if (copied == null || releaseTtlValueReferenceAfterRun && !copiedRef.compareAndSet(copied, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
//装载到当前线程
Map<TransmittableThreadLocal<?>, Object> backup = TransmittableThreadLocal.backupAndSetToCopied(copied);
try {
runnable.run();//执行提交的task
} finally {
//clear
TransmittableThreadLocal.restoreBackup(backup);
}
}
}
在上面的使用线程池的例子当中,如果换成这种修饰的方式进行操作,B任务得到的肯定是父线程中ThreadLocal的值,解决了在线程池中InheritableThreadLocal不能解决的问题。
更新父线程ThreadLocal值?
如果线程之间出了要能够得到父线程中的值,同时想更新值怎么办呢?在前面我们有提到,当子线程copy父线程的ThreadLocalMap的时候是浅拷贝的,代表子线程Entry里面的value都是指向的同一个引用,我们只要修改这个引用的同时就能够修改父线程当中的值了,比如这样:
@Override
public void run() {
System.out.println("========");
Span span= inheritableThreadLocal.get();
System.out.println(span);
span.name="liuliuliu";//修改父引用为liuliuliu
inheritableThreadLocal.set(new Span("zhangzhangzhang"));
System.out.println(inheritableThreadLocal.get());
}
这样父线程中的值就会得到更新了。能够满足父线程ThreadLocal值的实时更新,同时子线程也能共享父线程的值。不过场景倒是不是很常见的样子。
ThreadLocal父子线程传递实现方案相关推荐
- InheritableThreadLocal类原理简介使用 父子线程传递数据详解 多线程中篇(十八)...
上一篇文章中对ThreadLocal进行了详尽的介绍,另外还有一个类: InheritableThreadLocal 他是ThreadLocal的子类,那么这个类又有什么作用呢? 测试代码 publi ...
- 当ThreadLocal碰上线程池
ThreadLocal使用 ThreadLocal可以让线程拥有本地变量,在web环境中,为了方便代码解耦,我们通常用它来保存上下文信息,然后用一个util类提供访问入口,从controller层到s ...
- java传递父线程对象_父子线程和线程池如何实现threadLocal变量传递
上一次我们看了ThreadLocal的原理和实现,今天我们看看下面几个问题: 1.多线程中父子线程,子线程如何获取父线程的变量?2.主线程和线程池的线程本地副本变量如何实现复用隔离? 一.Inheri ...
- java 父子线程 调用链_ZipKin原理学习--Zipkin多线程及线程池中追踪一致性问题解决...
在学习Zipkin分布式追踪系统中我们了解到Trace在整个调用链是一致的,在web服务中可以通过在header设置Trace值在不同的服务中进行传递,那样在一个服务内部不同的线程,甚至是线程池中Zi ...
- InheritableThreadLocal——父线程传递本地变量到子线程的解决方式及分析
上一个博客提到ThreadLocal变量的基本使用方式,可以看出ThreadLocal是相对于每一个线程自己使用的本地变量,但是在实际的开发中,有这样的一种需求:父线程生成的变量需要传递到子线程中进行 ...
- JAVA跨线程传递数据方式总结
实现跨线程传递数据方式: v1:子线程使用主线程的局部变量 这种当主线程和子线程不在一快儿时就不适用.可以使用JDK原生的InheritableThreadLocal. v2:InheritableT ...
- 向线程传递数据与线程用回调方法检索数据
用回调方法检索数据 /**//*用回调方法检索数据 下面的示例演示了一个从线程中检索数据的回调方法.包含数据和线程方法的类的构造函数也接受代表回调方法的委托: 在线程方法结束前,它调用该回调委托. * ...
- ThreadLocal和线程同步机制的对比
ThreadLocal和线程同步都是为了解决多线程中相同变量的访问冲突问题,那么,二者的区别在哪里?和线程同步机制对比,ThreadLocal有什么优势? 同步机制中通过对象的锁机制保证同一时间只有一 ...
- 第五节:Task构造函数之TaskCreationOptions枚举处理父子线程之间的关系。
一. 整体说明 揭秘: 通过F12查看Task类的源码(详见下面的截图),发现Task类的构造函数有有一个参数为:TaskCreationOptions类型,本章节可以算作是一个扩展章节,主要就来研究 ...
最新文章
- make -j8以及linux下查看cpu的核数
- 面试官让我讲讲Unicode,我讲了3秒说没了,面试官说你可真菜
- 水晶报表主子报表分页问题
- DS1302时钟芯片在STM32F0中的完整应用代码
- SQL语句恢复数据库时一直显示“正在还原”
- Codeforces 959 E. Mahmoud and Ehab and the xor-MST 思路:找规律题,时间复杂度O(log(n))
- 当推荐系统遇到物联网...
- pytorch报错: invalid argument 0: Sizes of tensors must match except in dimension 0.
- sqlserver2005 openRowSet 和row_Number
- 动态SQL实现批量删除指定数据库的全部进程
- ubuntu防火墙问题
- paip.互联网产品要成功的要素
- 基于javaweb+SSM的药品管理系统(java+SSM+html+jQuery+Tomcat+mysql)
- 第一章 如何学习单片机
- Thinkpad笔记本电池保养
- typora中的图片加载不出来
- 中国古代兵器与兵书·铁马驰骋
- Jetson Orin 平台MAX9296+森云SG5-IMX490C-GMSL2 RGGB(无ISP)驱动调试
- 【Unityc#专题篇】—基础章题单实践
- aac格式怎么转换为MP3格式
热门文章
- sringboot security基本用法
- 《数据结构》c语言版学习笔记——其他链表(线性表的链式存储结构Part2)
- 《数据结构》c语言版学习笔记——单链表结构(线性表的链式存储结构Part1)
- 怎么用git将本地代码上传到远程服务器_git命令 将本地代码上传到远程服务器...
- 程序员圣诞节相册源码_程序员分享圣诞刷屏源码,这次朋友圈千万不要再@微信官方了!...
- centos7.6查询不到网卡信息
- liunx--账户文件权限和管理(账户添加删除,组的添加和删除 文件的归宿和权限)
- java连接数据库hei_如何黑MySQL5数据库?(来自:http://superhei.blogbus.com)
- ajax带来的主要问题有哪些,ajax面试题
- mysql 8安装_mysql安装过程详解