当前位置:首页 > 科技  > 软件

InheritableThreadLocal 是如何实现的父子线程局部变量的传递

来源: 责编: 时间:2024-07-09 18:20:03 84观看
导读今天聊一聊并发编程中经常遇到也是面试时容易被为难的一个题目,线程间局部变量的传递问题。相信对并发编程有一定了解的同学已经想到了大名鼎鼎的 ThreadLocal 了,是的,线程内部就是通过 inheritableThreadLocals 实现了

今天聊一聊并发编程中经常遇到也是面试时容易被为难的一个题目,线程间局部变量的传递问题。xNI28资讯网——每日最新资讯28at.com

相信对并发编程有一定了解的同学已经想到了大名鼎鼎的 ThreadLocal 了,是的,线程内部就是通过 inheritableThreadLocals 实现了父子线程间局部变量的传递。xNI28资讯网——每日最新资讯28at.com

JDK 8xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

一、父子线程间局部变量参数传递的方式 ThreadLocal

首先我们写看一段代码:xNI28资讯网——每日最新资讯28at.com

public class ThreadLocalTest implements Runnable{    private static final InheritableThreadLocal<String> MAIN_THREAD_LOCAL = new InheritableThreadLocal<>();    @SneakyThrows    @Override    public void run() {        System.out.println("threadlocal 默认值:"+ThreadLocalTest.MAIN_THREAD_LOCAL.get());        MAIN_THREAD_LOCAL.set("child thread value :"+Thread.currentThread().getName());        System.out.println("threadlocal 设置子线程值之后:"+ThreadLocalTest.MAIN_THREAD_LOCAL.get());    }    public String get(){        return MAIN_THREAD_LOCAL.get();    }    public void clean(){        MAIN_THREAD_LOCAL.remove();    }    public static void main(String[] args) {        ThreadLocalTest threadLocalTest = new ThreadLocalTest();        MAIN_THREAD_LOCAL.set("父线程的值 set 111");        System.out.println("启动:"+threadLocalTest.get());        for (int i = 0; i < 3; i++) {            new Thread(threadLocalTest).start();//            ThreadUtil.execAsync(threadLocalTest);        }        System.out.println("结束:"+threadLocalTest.get());    }}

在上面的这段代码中,我们就做了三个事情:xNI28资讯网——每日最新资讯28at.com

  • 设置父线程中定义ThreadLocal的值。
  • 在子线程中打印父线程中ThreadLocal的值。
  • 启动多个子线程

大家可以先猜一下这段代码的运行结果。xNI28资讯网——每日最新资讯28at.com

二、子线程可以继承父线程局部变量的值吗

首先我们先说下答案,是可以继承的。上面代码的执行结果如下:xNI28资讯网——每日最新资讯28at.com

启动:父线程的值 set 111结束:父线程的值 set 111threadlocal 默认值:父线程的值 set 111threadlocal 设置子线程值之后:child thread value :Thread-1threadlocal 默认值:父线程的值 set 111threadlocal 默认值:父线程的值 set 111threadlocal 设置子线程值之后:child thread value :Thread-2threadlocal 设置子线程值之后:child thread value :Thread-0

在上面的代码中,我们的子线程优先打印了父线程中ThreadLocal的值,然后重新设置该值,再次读取。得出结论就是子线程可以通过ThreadLocal继承父线程的值,并且子线程自己内容再次重新设置不影响父线程的值。xNI28资讯网——每日最新资讯28at.com

三、父子线程局部变量传值的原理

难道一句简单的ThreadLocal就可以让我们对这个问题停止探索吗?那么线程内部是如何通过ThreadLocal进行传值的呢?xNI28资讯网——每日最新资讯28at.com

1.new thread

在上面代码中,启动子线程的方式是new Thread(threadLocalTest).start();,所以秘密一定就在这一行代码里面。源码之下无秘密,我们一起来看下。xNI28资讯网——每日最新资讯28at.com

首先进入new Thread()的内部:xNI28资讯网——每日最新资讯28at.com

    public Thread(Runnable target) {        init(null, target, "Thread-" + nextThreadNum(), 0);    }    private void init(ThreadGroup g, Runnable target, String name,                      long stackSize) {        init(g, target, name, stackSize, null, true);    }

通过上面两个方法调用,最终进入到下面这个方法中:xNI28资讯网——每日最新资讯28at.com

 private void init(ThreadGroup g, Runnable target, String name,                      long stackSize, AccessControlContext acc,                      boolean inheritThreadLocals) {}

init方法有个参数inheritThreadLocals,boolean类型的,如果为true,且可继承的线程局部变量不为空就继承。xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

现在我们只需要顺着inheritThreadLocals这个参数去找就可以了,在Thread的418行,有这样一行代码。(代码行数可能因版本而位置不同)xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

可以看到是直接对当前线程的inheritableThreadLocals直接进行的赋值操作,而值是通过ThreadLocal.createInheritedMap获取的,下面我们看下这个createInheritedMap方法做了哪些操作?xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

createInheritedMap方法是ThredLocal内部的方法,接收传递父线程的ThreadLocalMap为参数,该方法只做了一个事情,就是new了一个新的ThreadLocalMap。xNI28资讯网——每日最新资讯28at.com

跟进到new ThreadLocalMap(parentMap)方法内部,其实是把传进的值,一个个的遍历进行赋值到当前线程中。xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

对于图中标记的第二个地方,childValue调用的是InheritableThreadLocal#childValue,该方法内也只做了一件事,就是返回传进来的值。xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

(1) 小结xNI28资讯网——每日最新资讯28at.com

父子线程之所以能传参,是因为我们使用了InheritableThreadLocal,这样在new Thread()时,就会进入到给子线程赋值父线程inheritableThreadLocals的逻辑中去。xNI28资讯网——每日最新资讯28at.com

(2) 扩展xNI28资讯网——每日最新资讯28at.com

有的同学会说了,我用 ThreadLocal.withInitial创建的,怎么走到线程的if (inheritThreadLocals && parent.inheritableThreadLocals != null)判断时,没有进去呢,上面不是说是在这判断然后对子线程进行赋值的吗?xNI28资讯网——每日最新资讯28at.com

在这简单说一下哈,大家在写代码时,或者再用第三方框架时,源码中的注释一定要看仔细,很多细节都在注释中标注清楚了。xNI28资讯网——每日最新资讯28at.com

    public static ThreadLocal<String> MAIN_THREAD_LOCAL = ThreadLocal.withInitial(() -> "父线程的值 withInitial 111");

在上面的代码中,我们进行了ThreadLocal的初始化赋值,然后看下withInitial方法。xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

所以是当调用get方法时,才会触发赋值的操作,那么我们看下get方法。xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

如果当前线程的局部变量没有值,返回初始化方法初始的值。xNI28资讯网——每日最新资讯28at.com

所以对于我们来说就是SuppliedThreadLocal#initialValue返回的值。xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

2.线程池

刚才我们是通过new Thread()启动的子线程,可是工作中基本都是通过线程池的方式执行任务的啊,那还生效吗?xNI28资讯网——每日最新资讯28at.com

答案是生效。xNI28资讯网——每日最新资讯28at.com

我们使用hutool工具中的ThreadUtil.execAsync(threadLocalTest);进行测试。xNI28资讯网——每日最新资讯28at.com

直接说结论,感兴趣的同学可以自行修改一下代码中的子线程启动方式。xNI28资讯网——每日最新资讯28at.com

先画个流程图,大家可以跟着代码走一下。xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

当使用线程池时,底层原理还是线程池中放入任务的逻辑,当放入线程池之后,会在AbstractExecutorService#submit()方法中执行execute方法,最终执行在ThreadPoolExecutor#execute(),在这里,就是把任务丢入线程池工作的逻辑,其中有个方法addWorker,该方法中有一行new Worker(),而在该Worker方法的内部,其实就是new Thread(),到了这,就与上面所说的一样了,到了判断inheritableThreadLocals的时候了。xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

四、如何解决内存泄漏

使用ThreadLocal的应用场景有很多,父子线程传参数的场景也有不少,但是有一个很关键的点内存溢出是需要重视的。解决ThreadLocal内存溢出的方式也很简单,就是在使用完成之后调用一下remove。xNI28资讯网——每日最新资讯28at.com

对于上面的代码示例,就是调用我们的clean方法。xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

public void clean(){ MAIN_THREAD_LOCAL.remove();}

xNI28资讯网——每日最新资讯28at.com

remove的代码如下,取值不为null时,执行删除逻辑。xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

xNI28资讯网——每日最新资讯28at.com

五、总结

我们通过一个示例,验证了父子线程间可以通过ThreadLocal进行传递,测试了不同方式初始化ThreadLocal,并对比了new Thread()与线程池启动的区别。xNI28资讯网——每日最新资讯28at.com

其实殊途同归,线程池最后调用的还是Thread里面的方法。唯一需要注意的就是通过ThreadLocal.withInitial初始化是在get时赋值的,不过这个应该也不重要,了解一下就好,应该也没有面试官会这么抠这个问题吧。xNI28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-99899-0.htmlInheritableThreadLocal 是如何实现的父子线程局部变量的传递

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 利用依赖结构矩阵管理架构债务

下一篇: 软件架构中的九种耦合形式

标签:
  • 热门焦点
  • 影音体验是真的强 简单聊聊iQOO Pad

    影音体验是真的强 简单聊聊iQOO Pad

    大公司的好处就是产品线丰富,非常细分化的东西也能给你做出来,例如早先我们看到了新的vivo Pad2,之后我们又在iQOO Neo8 Pro的发布会上看到了iQOO的首款平板产品iQOO Pad。虽
  • 7月安卓手机好评榜:三星S23Ultra好评率第一

    7月安卓手机好评榜:三星S23Ultra好评率第一

    性能榜和性价比榜之后,我们来看最后的安卓手机好评榜,数据来源安兔兔评测,收集时间2023年7月1日至7月31日,仅限国内市场。第一名:三星Galaxy S23 Ultra好评率:95.71%在即将迎来新
  • 7月安卓手机性能榜:红魔8S Pro再夺榜首

    7月安卓手机性能榜:红魔8S Pro再夺榜首

    7月份的手机市场风平浪静,除了红魔和努比亚带来了两款搭载骁龙8Gen2领先版处理器的新机之外,别的也想不到有什么新品了,这也正常,通常6月7月都是手机厂商修整的时间,进入8月份之
  • 帅气纯真少年!日本最帅初中生选美冠军出炉

    帅气纯真少年!日本最帅初中生选美冠军出炉

    日本第一帅哥初一生选美大赛冠军现已正式出炉,冠军是来自千叶县的宗田悠良。日本一直热衷于各种选美大赛,从&ldquo;最美JK&rdquo;起到&ldquo;最美女星&r
  • 学习JavaScript的10个理由...

    学习JavaScript的10个理由...

    作者 | Simplilearn编译 | 王瑞平当你决心学习一门语言的时候,很难选择到底应该学习哪一门,常用的语言有Python、Java、JavaScript、C/CPP、PHP、Swift、C#、Ruby、Objective-
  • 十个简单但很有用的Python装饰器

    十个简单但很有用的Python装饰器

    装饰器(Decorators)是Python中一种强大而灵活的功能,用于修改或增强函数或类的行为。装饰器本质上是一个函数,它接受另一个函数或类作为参数,并返回一个新的函数或类。它们通常用
  • 在线图片编辑器,支持PSD解析、AI抠图等

    在线图片编辑器,支持PSD解析、AI抠图等

    自从我上次分享一个人开发仿造稿定设计的图片编辑器到现在,不知不觉已过去一年时间了,期间我经历了裁员失业、面试找工作碰壁,寒冬下一直没有很好地履行计划.....这些就放在日
  • iQOO Neo8系列今日官宣:首发天玑9200+ 全球安卓最强芯!

    iQOO Neo8系列今日官宣:首发天玑9200+ 全球安卓最强芯!

    在昨日举行的的联发科新一代旗舰芯片天玑9200+的发布会上,iQOO官方也正式宣布,全新的iQOO Neo8系列新品将全球首发搭载这款当前性能最强大的移动平台
  • 联想小新Pad Pro 12.6将要推出,搭载高通骁龙 870 处理器

    联想小新Pad Pro 12.6将要推出,搭载高通骁龙 870 处理器

    联想小新Pad Pro 12.6将于秋季新品会上推出,官方按照惯例直接在发布会前给出了机型的所有参数。联想小新 Pad Pro 12.6 将搭载高通骁龙 870 处理器,重量为 5
Top