java怎么拦截某个对象
256
2022-08-24
精通Java并发编程N+4:InheritableThreadLocal(可继承的ThreadLocal)详解
一、前言
前一段时间做了个服务调用链路追踪的需求,最后需要把trace信息通过Mybatis Plugin持久化到每个业务表中;以供后面的日志审计服务使用。 其中我采用ThreadLocal把trace信息传递到Mybatis Plugin中,不过由于存在 在dubbo接口 / Rest接口中采用new Threa()、CompleteFuture的方式启动一个异步线程去做DB数据(走MyBatis)的持久化,这时ThreadLocal就无法将trace信息传递到Mybatis Plugin,因此有了今天InheritableThreadLocal的故事。
二、InheritableThreadLocal概述
InheritableThreadLocal用于实现 父线程生成的变量可以自动传递到子线程中进行使用 的需求。
从类的构造来看,InheritableThreadLocal继承自ThreadLocal;
在编码使用上,InheritableThreadLocal和ThreadLocal没有任何区别,只是在实例化的对象的时候使用 new InheritableThreadLocal<>()。
1、ThreadLocal的使用
public class ParentChildThreadLocal { public final static ThreadLocal
上述代码中,实例化一个ThreadLocal变量,在main线程中对ThreadLocal变量进行赋值、取值,并在main线程中开启一个子线程获取main线程中ThreadLocal变量的值。执行结果如下:
从运行结果可以看出,使用ThreadLocal时,子线程无法获取到父线程中的本地变量值。
2、InheritableThreadLocal的使用
public class ParentChildThreadLocal { public final static InheritableThreadLocal
上述代码中,实例化一个InheritableThreadLocal变量,在main线程中对InheritableThreadLocal变量进行赋值、取值,并在main线程中开启一个子线程获取main线程中InheritableThreadLocal变量的值。执行结果如下:
从运行结果可以看出,使用InheritableThreadLocal之后,子线程可以获取到父线程中的本地变量值。
那么为什么呢?下面来跟一下InheritableThreadLocal的源码。
三、InheritableThreadLocal源码分析
经过上面的测试代码,我们可以推测出InheritableThreadLocal是ThreadLocal派生出专门用于解决线程本地变量父传子问题的,这里我们通过源码分析一下InheritableThreadLocal是如何完成这一操作的!
1、Thread类构成
Thread类中包含 threadLocals 和 inheritableThreadLocals 两个变量,其中 inheritableThreadLocals 用于存储可自动从父线程向子线程中传递的ThreadLocal.ThreadLocalMap。
1> Thread#inheritableThreadLocals变量
接着,来看一下创建线程时如何实现ThreadLocal在父子线程之间的传递;
而此处所谓的父线程,指想调用new Thread()实例化一个线程的当前线程。
2> ThreadLocal#createInheritedMap()
再看ThreadLocal#createInheritedMap()方法是如何将父线程中的inheritableThreadLocals传递到子线程的。
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }
本质上就是实例化一个ThreadLocalMap:
private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); // ThreadLocalMap 使用 Entry[] table 存储ThreadLocal table = new Entry[len]; // 逐一复制 parentMap中的所有ThreadLocal记录 for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal
由于ThreadLocalMap内部存在hash冲突的情况,所有采用开发地址法解决hash冲突问题。
即数据想放在索引下标为3,但是索引下标3处存在数据,此时就从索引下标4开始往后查找空闲的位置。相应的它的查询速度就会慢很多,这是一种时间换空间的做法。
从这里可以推测出,InheritableThreadLocal主要便是针对Thread类的inheritableThreadLocals 变量做操作。
2、InheritableThreadLocal类结构
首先,InheritableThreadLocal类继承自ThreadLocal;
然后重写了ThreadLocal的三个方法:childValue(T parentValue)、getMap(Thread t)、createMap(Thread t, T firstValue)
public class InheritableThreadLocal
1> ThreadLocal#set()方法
走到ThreadLocal#set()方法里:
上面提到,InheritableThreadLocal重写了ThreadLocal的三个方法,其中就包括getMap(Thread t)、createMap(Thread t, T firstValue);也就是说ThreadLocal存储数据的主体从Thread的threadLocals变量变成了inheritableThreadLocals变量;
而对于其余操作并无区别;
因为hash计算、查找、扩容都是ThradLocalMap里做的,而InheritableThreadLocal只决定为Thread对象里哪个ThreadLocalMap属性赋值。
2> ThreadLocal#get()方法
ThreadLocal#get()方法和set()方法一样,只是操作的Thread的ThradLocalMap对象不同。
3> 阶段总结,问:InheritableThreadLocal和ThreadLocal的区别是什么?
InheritableThreadLocal继承自ThreadLocal,用于解决父子线程之间传递变量的问题。而InheritableThreadLocal之所以能在父子线程之间传递变量,是因为其使用的存储线程ThreadLocal数据的主体ThreadLocalMap是Thread类中的inheritableThreadLocals,而ThreadLocal使用的是Thread类中的threadLocals。在线程初始化的时候,会从父线程中取出inheritableThreadLocals信息传递到子线程。所以InheritableThreadLocal和ThreadLocal的唯一区别在于:它俩存储数据的ThreadLocalMap主体不同。
三、InheritableThreadLocal有什么问题吗?
1、直接通过set改变对象内容时,线程不安全
如果线程本地变量是可写的,那么任意子线程针对本地变量的修改都会影响到主线程的本地变量(本质上是同一个对象)。
这里对于共享变量的修改存在线程安全问题太正常了。主要点在于线程池中InheritableThreadLocal失效。
2、线程池中InheritableThreadLocal失效
在使用线程池时,InheritableThreadLocal会完全失效;因为父线程的ThreadLocalMap是通过实例化一个Thread时赋值给子线程的,而线程池在执行异步任务时可能不需要创建新的线程,因此也就不会再传递父线程的ThreadLocalMap给子线程。
针对这个问题,我们可以通过使用阿里开源的TransmittableThreadLocal解决,参考文档:具体内容下篇博文输出。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~