精通Java并发编程N+4:InheritableThreadLocal(可继承的ThreadLocal)详解

网友投稿 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 = new ThreadLocal<>(); public static void main(String[] args) { threadLocal.set("haha, parent-child variables!"); System.out.println("父线程的值(threadLocal):" + threadLocal.get()); // 开启子线程 new Thread(() -> { System.out.println("子线程继承到的值(threadLocal): " + threadLocal.get()); }).start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } }}

上述代码中,实例化一个ThreadLocal变量,在main线程中对ThreadLocal变量进行赋值、取值,并在main线程中开启一个子线程获取main线程中ThreadLocal变量的值。执行结果如下:

从运行结果可以看出,使用​​ThreadLocal​​时,子线程无法获取到父线程中的本地变量值。

2、InheritableThreadLocal的使用

public class ParentChildThreadLocal { public final static InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal<>(); public static void main(String[] args) { inheritableThreadLocal.set("haha, parent-child variables!"); System.out.println("父线程的值(InheritableThreadLocal):" + inheritableThreadLocal.get()); // 开启子线程 new Thread(() -> { System.out.println("子线程继承到的值(InheritableThreadLocal): " + inheritableThreadLocal.get()); }).start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } }}

上述代码中,实例化一个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 key = (ThreadLocal) e.get(); if (key != null) { // childValue()内部是直接将e.value返回 Object value = key.childValue(e.value); // 构建Entry数组节点,放入到ThreadLocalMap中 Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); // 使用开发地址方法解决hash冲突 while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } }}

由于​​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 extends ThreadLocal { protected T childValue(T parentValue) { return parentValue; } /** * Get the map associated with a ThreadLocal. * * @param t the current thread */ ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; } /** * Create the map associated with a ThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the table. */ void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); }}

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小时内删除侵权内容。

上一篇:收藏!内容营销最全内容策略!
下一篇:使用Linux auto Makefile自动生成的运行步骤
相关文章

 发表评论

暂时没有评论,来抢沙发吧~