上一篇中老吕介绍了ThreadLocal线程数据绑定的原理,今天聊聊父子线程之间如何继承ThreadLocal上维护的数据。

开发过程中异步执行任务有两种情况,第一种情况是 主线程 通过 new Thread()的方式产生了一个子线程,然后把 task 交给子线程去执行;第二种情况是主线程将task提交到线程池去执行。不同的情况需要不同的方案解决。

第一种情况:

通过InheritableThreadLocal来代替ThreadLocal

先写个例子测试下:

public class Test1 {public static void main(String[] args) {InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();threadLocal.set("userId-1001");System.out.println("父线程set:userId-1001");Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("子线程get:"+threadLocal.get());}});thread.start();System.out.println("over");while (true);}
}测试结果正常实现预期目标:
父线程set:userId-1001
子线程get:userId-1001
over

InheritableThreadLocal是ThreadLocal的子类,它增加的功能就是可以继承父线程上绑定的数据,下面看下它的源码是如何做到的继承:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {//这个方法的意义是在从父到子复制数据过程中,如果想修改,可以覆盖这个方法,这里没有做任何修改,直接用的父线程的值protected T childValue(T parentValue) {return parentValue;}
//这个方法体现了ThreadLocal类和InheritableThreadLocal类的区别,数据存储的位置不同
//在InheritableThreadLocal中数据是存储在 Thread对象的 inheritableThreadLocals中
//而ThreadLocal中数据是存储在Thread对象的 threadLocals中ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;}//createMap和getMap是对应的,创建map时放到 Thread对象的 inheritableThreadLocals 中 void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);}
}

我们再来看下在产生子线程过程中继承父线程的数据是如何实现的

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);
}private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {。。。省略了无关代码//这就是继承的逻辑,inheritThreadLocals默认是true,并且父线程中inheritableThreadLocals不为null//复制过程在 ThreadLocal.createInheritedMap 方法中if (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);。。。省略了无关代码
}//new 了一个新的 ThreadLocalMap对象
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {return new ThreadLocalMap(parentMap);
}//把key!=null 的Entry 复制过来,浅复制,key共用,value也是共用
private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);table = new Entry[len];for (int j = 0; j < len; j++) {Entry e = parentTable[j];if (e != null) {@SuppressWarnings("unchecked")ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();if (key != null) {//这个childValue的意义就是你想修改数据时就覆盖,//而在InheritableThreadLocal中是原值返回,不做任何修改Object value = key.childValue(e.value);Entry c = new Entry(key, value);int h = key.threadLocalHashCode & (len - 1);while (table[h] != null)h = nextIndex(h, len);table[h] = c;size++;}}}
}

可以看到创建了新的ThreadLocalMap对象,新的Entry,但是里面的key和value是复用的父线程中的对象。

至此InheritableThreadLocal类就讲清楚了,它适用的情况就是 子线程必须是主线程临时创建的。

第二种情况:针对线程池这种情况

方案1:通过阿里开源组件transmittable-thread-local解决

https://github.com/alibaba/transmittable-thread-local

方案2:手撕一个简单组件解决

思路:写个代理类,代理Runnable和Callable类

/*** 代理类:增加ThreadLocal数据传递功能*/
class TaskProxy<V> implements Runnable, Callable {private Runnable runnable;private Callable<V> callable;public TaskProxy(Runnable runnable){this.runnable = runnable;storeThreadLocal();}public TaskProxy(Callable callable){this.callable = callable;storeThreadLocal();}@Overridepublic void run() {restoreThreadLocal();this.runnable.run();clearThreadLocal();}@Overridepublic Object call() throws Exception {restoreThreadLocal();V v = this.callable.call();clearThreadLocal();return v;}//------------------------绑定的数据-----------private String userId;private String traceId;private void storeThreadLocal() {this.userId = ThreadLocalUtil.getUserId();this.traceId = ThreadLocalUtil.getTraceId();}private void restoreThreadLocal() {ThreadLocalUtil.setUserId(userId);ThreadLocalUtil.setTraceId(traceId);}private void clearThreadLocal() {ThreadLocalUtil.removeUserId();ThreadLocalUtil.removeTraceId();}}/*** ThreadLocal工具类*/
class ThreadLocalUtil{//可以使用一个自定义 上下文DTO 来存储数据,就不需要写多个ThreadLocal了private static ThreadLocal<String> userIdThreadLocal = new ThreadLocal<>();private static ThreadLocal<String> traceIdThreadLocal = new ThreadLocal<>();public static void setUserId(String userId){userIdThreadLocal.set(userId);}public static void setTraceId(String traceId){traceIdThreadLocal.set(traceId);}public static String getUserId(){return userIdThreadLocal.get();}public static String getTraceId(){return traceIdThreadLocal.get();}public static void removeUserId(){userIdThreadLocal.remove();}public static void removeTraceId(){traceIdThreadLocal.remove();}
}//测试下
public class Test2 {static ExecutorService executorService = Executors.newFixedThreadPool(1);{executorService.submit(new Runnable() {@Overridepublic void run() {System.out.println("预热,产生核心线程");}});}public static void main(String[] args) {//-----主线程绑定数据----ThreadLocalUtil.setUserId("1001");ThreadLocalUtil.setTraceId("o98iuj76yhe3");//复用核心线程,未使用代理executorService.submit(new Runnable() {@Overridepublic void run() {System.out.println("未使用代理:"+ThreadLocalUtil.getUserId());System.out.println("未使用代理:"+ThreadLocalUtil.getTraceId());}});//复用核心线程,Runnable使用代理executorService.submit((Runnable) new TaskProxy(new Runnable() {@Overridepublic void run() {System.out.println("使用代理Runnable:"+ThreadLocalUtil.getUserId());System.out.println("使用代理Runnable:"+ThreadLocalUtil.getTraceId());}}));//复用核心线程,Callable使用代理executorService.submit((Callable) new TaskProxy<String>(new Callable() {@Overridepublic String call() throws Exception {System.out.println("使用代理Callable:"+ThreadLocalUtil.getUserId());System.out.println("使用代理Callable:"+ThreadLocalUtil.getTraceId());return "ok";}}));System.out.println("over");while (true);}
}//测试结果--使用了代理类的都能正常传递数据
未使用代理:null
未使用代理:null
使用代理Runnable:1001
使用代理Runnable:o98iuj76yhe3
使用代理Callable:1001
使用代理Callable:o98iuj76yhe3

总结

本文详述了InheritableThreadLocal的实现原理,

ThreadLocal和InheritableThreadLocal的区别,

以及如何解决不同情况下ThreadLocal的数据传递问题,

大家可以根据自己的需要选择不同的方案。

JDK源码系列:子线程如何继承父线程上通过ThreadLocal绑定的数据相关推荐

  1. 大白话讲解JDK源码系列:从头到尾再讲一遍ThreadLocal

    引言 其实网上有很多关于ThreadLocal的文章了,有不少文章也已经写的非常好了.但是很多同学反应还有一些部分没有讲解的十分清楚,还是有一定的疑惑没有想的十分清楚.因此本文主要结合常见的一些疑问. ...

  2. HashSet源码分析:JDK源码系列

    1.简介 继续分析源码,上一篇文章把HashMap的分析完毕.本文开始分析HashSet简单的介绍一下. HashSet是一个无重复元素集合,内部使用HashMap实现,所以HashMap的特征耶继承 ...

  3. JDK源码系列(2)-Object类

    引言 我们都知道,在Java中,Object是所有类的超类,所有的类其实都是隐含继承自Object类的,所以extends Object默认是不用写的,当然你写了也不会错.所有的类都可以使用Objec ...

  4. JDK源码系列:Future是如何实现的?

    大家好,我们在异步编程时向线程池提交(submit)一个任务后会得到一个 Future对象,通过 future.get() 方法可以堵塞等待结果的完成,例如: public static void m ...

  5. JDK源码系列:synchronized与wait、notify、notifyAll

    大家好,今天聊一聊synchronized与obj.wait().obj.notify().obj.notifyAll() 之间的关系以及它们的实现原理. 我们今天采用边写demo边分析的方式来进行. ...

  6. JDK源码系列:AQS(队列同步器)原理

    大家好,好久不见,今天看下JDK中的JUC包中AQS(AbstractQueuedSynchronizer 队列同步器)的实现原理. JUCL下的锁和synchronized提供的锁的区别 1.锁的获 ...

  7. vue源码深入解读MVVM(视图模板引擎),你真的了解双向绑定(v-model),数据劫持(observe),发布订阅模式吗?带你手鲁mvvm引擎。源码奉上(详细注释)!

    文章目录 #1.vue的强大之处不必细说,vue的核心v-model的实现原理,网上都有很多.但是真正自己实现双向绑定,mvvm的源码却几乎没见过. #1.2本人根据源码的解读,理解,以及借鉴网上的视 ...

  8. JDK源码系列:ThreadLocal弱引用真的是过度设计吗?

    在<码处高效:Java开发手册>这本书上详细描述了ThreadLocal的原理,也有过度设计的说法, 难道弱引用设计真的没必要吗?对此老吕要仔细分析分析,ThreadLocal到底该不该使 ...

  9. JDK源码系列(6)-StringBuilder

    一.概述 StringBuilder是一个可变的字符串序列,这个类被设计去兼容StringBuffer类的API,但不保证线程安全性,是StringBuffer单线程情况下的一个替代实现.在可能的情况 ...

最新文章

  1. DCASE 2020权威声学比赛:腾讯多媒体实验室斩获双项指标国内第一
  2. Oracle查询所有表结构和表名称及备注
  3. mysql 特殊字符支持_mysql 解决生僻字,特殊字符插入失败
  4. 实现userdetails_Spring Security使用Hibernate实现自定义UserDetails
  5. 米斯特白帽培训讲义 工具篇 BruteXSS
  6. 在Simulink中设计多工位的系列PID控制器Design Family of PID Controllers for Multiple Operating Points
  7. 【甘道夫】HBase基本数据操作的详细说明【完整版,精绝】
  8. Linux修行学习,网站持更
  9. python - class类 (七) 三大特性 - 封装 结尾
  10. HTML5缓存之 WebStorage
  11. 7.Linux 高性能服务器编程 --- Linux 服务器程序规范
  12. MySQL空间索引简单使用
  13. python设计模式之MVC
  14. QTP11.5/UFT官方下载与安装
  15. #ardiuno #蓝牙 #if函数判断 #串口中米思齐
  16. win10文件索引服务器,Win10系统修改索引文件夹路径的方法
  17. java论坛 基于SSM框架的游戏论坛 java游戏贴吧 java游戏论坛 java论坛 ssm论坛 ssm贴吧 可以改为各种论坛,分类可在后台自己控制,图片可任意换
  18. EBS INV:单位
  19. GIT 命令学习:获取与创建项目
  20. 制作本地SCLo-scl镜像仓库(reposync下载rpm包、createrepo制作镜像仓库、httpd发布服务)

热门文章

  1. css字体像素教程,详解CSS中的字体属性的使用
  2. 北斗导航系统、GPS、GLONASS信号频率
  3. 计算机辅助设计毕业论文,快速阀的计算机辅助设计毕业设计.doc
  4. 树莓派上实现TSL2561对光照强度的获取
  5. 符号三角形问题—回溯算法—java实现
  6. 简述力法计算弹性固定无铰拱的原理_实用建筑结构静力计算手册的目录
  7. 将16进制字符串转化成内存二进制数据
  8. 在slicer中编写scripted模块
  9. 求两个小数的“最小公倍数”
  10. 地铁逃生的服务器正在维护吗,LifeKeeper为北京地铁保护维护系统