【JUC进阶】13. InheritableThreadLocal

目录


1、前言

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

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

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

2、回顾ThreadLocal

static ThreadLocal<String> threadLocal = new ThreadLocal<>();

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

    new Thread(() -> {
        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<String> threadLocal = new InheritableThreadLocal<>();

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

    new Thread(() -> {
        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变量赋值给当前线程的inheritableThreadLocals
    this.inheritableThreadLocals =
        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...

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

5、线程池中的问题

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

static ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();

// 定义线程池,核心线程数为1,方便线程复用
static ExecutorService executorService = Executors.newSingleThreadExecutor();

public static void main(String[] args) throws InterruptedException {

    // 线程池执行子线程
    executorService.submit(() -> {
        System.out.println("-----> 子线程" + Thread.currentThread() + " <----- 获取threadlocal变量:" + threadLocal.get());
    });

    // 主线程睡眠3s,模拟运行
    Thread.sleep(3000);
    // 将变量修改为11111
    threadLocal.set("我是主线程的threadlocal变量,变量值为:11111");

    // 这里线程池重新执行线程任务
    executorService.submit(() -> {
        System.out.println("-----> 子线程" + Thread.currentThread() + " <----- 获取threadlocal变量:" + threadLocal.get());
    });

    // 线程池关闭
    executorService.shutdown();

}

执行结果:

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

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

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

6、小结

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

版权声明:本文为博主作者:有一只柴犬原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/p793049488/article/details/135466174

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2024年1月16日
下一篇 2024年1月16日

相关推荐