目录

1、前言

2、回顾ThreadLocal

3、InheritableThreadLocal

4、实现原理

5、线程池中的问题

6、小结


1、前言

在《【JUC基础】14. ThreadLocal》一文中,介绍了ThreadLocal主要是用于每个线程持有的独立变量。通俗的说就是ThreadLocal是每个线程独有的一份内存,且各个线程间是独立、隔离的。但是随之而来的便会带来如下问题:

  • 如果项目实际场景中,确实需要子线程与父线程共享或复用变量时候,就无法满足。

上面问题的一个解法就是我们今天要介绍的InheritableThreadLocal。

2、回顾ThreadLocal

static ThreadLocal threadLocal = new ThreadLocal();public static void main(String[] args) {threadLocal.set("我是主线程的threadlocal变量");System.out.println("-----> 主线程" + Thread.currentThread() + "  {System.out.println("-----> 子线程" + Thread.currentThread() + " <----- 获取threadlocal变量:" + threadLocal.get());}, "son-thread").start();}

执行结果:

可以看出子线程想要获取父线程的threadlocal变量,是获取不到的。

3、InheritableThreadLocal

前面介绍了背景,那么InheritableThreadLocal是啥呢?他可以做一些啥?从类注释上可以看出InheritableThreadLocal实现了ThreadLocal的扩展,以提供从父线程到子线程的值继承。当创建子线程时,子线程接收父线程有值的所有可继承的线程局部变量的初始值。当在变量中维护每线程属性(例如,User ID)时,优先使用可继承的线程局部变量,而不是普通的线程局部变量。

我们将上面ThreadLocal的demo中,ThreadLocal改为InheritableThreadLocal试下:

static InheritableThreadLocal threadLocal = new InheritableThreadLocal();public static void main(String[] args) {threadLocal.set("我是主线程的threadlocal变量");System.out.println("-----> 主线程" + Thread.currentThread() + "  {System.out.println("-----> 子线程" + Thread.currentThread() + " <----- 获取threadlocal变量:" + threadLocal.get());}, "son-thread").start();}

执行结果:

可以发现,主线程的变量成功穿透到子线程中。

4、实现原理

结果都看到了,但是我们肯定不能只满足于结果,我们来探究一下他是如何实现的。我们点进去InheritableThreadLocal可以看到,他是ThreadLocal的扩展,且重新实现了childValue(),getMap(),createMap()三个方法。

我们查看createMap()方法,可以看到inheritableThreadLocals变量其实是Thread内部定义的用于线程间共享(inheritable英译:遗传)的变量。

void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);}

Thread.java:

/* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

接着查看inheritableThreadLocals是从哪里赋值的:

重点关注画圈的部分,点进去java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long, java.security.AccessControlContext, boolean),查看代码420行:

...// 判断inheritThreadLocals为true,我们创建线程new Thread会进入初始化init方法,默认是true// 且判断parent.inheritableThreadLocals不为空if (inheritThreadLocals && parent.inheritableThreadLocals != null)// 进入该判断,将父线程的inheritableThreadLocals变量赋值给当前线程的inheritableThreadLocalsthis.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);...

以上的源码就是InheritableThreadLocal如何实现父子线程变量共享的实现原理了。

5、线程池中的问题

其实不难看出,InheritableThreadLocal只是解决了父子线程共享,或者变量传递的问题。接下来我们改造一下代码,我们通过线程管理多个线程试试看,然后把threadlocal的赋值操作放在创建线程之后:

static ThreadLocal threadLocal = new InheritableThreadLocal();// 定义线程池,核心线程数为1,方便线程复用static ExecutorService executorService = Executors.newSingleThreadExecutor();public static void main(String[] args) throws InterruptedException {// 线程池执行子线程executorService.submit(() -> {System.out.println("-----> 子线程" + Thread.currentThread() + "  {System.out.println("-----> 子线程" + Thread.currentThread() + " <----- 获取threadlocal变量:" + threadLocal.get());});// 线程池关闭executorService.shutdown();}

执行结果:

怎么又拿不到了?没错,上面提到InheritableThreadLocal实现值传递主要是根据父线程的map是否有值,再决定要不要赋值给子线程。而父线程的map是通过init一个Thread的时候赋值的。如果我们新创建一个线程,那么肯定会出发创建的初始化方法,必然会进行赋值操作。但是线程池由于线程复用,重复使用的线程在执行异步任务时可能无需再执行创建方法了,因此也就不会再传递父线程的TLMap给子线程了。自然后面获取到的就是null了。

总而言之,就是InheritableThreadLocal进行传递的必须是线程创建的时候赋值的才可以,如果是异步任务中进行赋值的一样是获取不到。如果是线上环境,那么此类问题一般都是偶发的,很容易把你搞脱发。

看到这,我知道你很急,但是你别急。太阳底下无新鲜事,我们不是第一个遇到此类问题的人,别人肯定也遇到过,看看业界是如何实现的。这就是我们接下来要介绍的TransmittableThreadLocal。欲知后事如何,请听下回分解~

6、小结

JUC编程中,往往遇到的问题都不是必现的,具备一定的JUC相关技术基础,可以给你在排障的路上减少一些阻碍。一起学习进步吧。