JDK源码系列:子线程如何继承父线程上通过ThreadLocal绑定的数据
上一篇中老吕介绍了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-1001over
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();}public void run() {restoreThreadLocal();this.runnable.run();clearThreadLocal();}public 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() {public void run() {System.out.println("预热,产生核心线程");}});}public static void main(String[] args) {//-----主线程绑定数据----ThreadLocalUtil.setUserId("1001");ThreadLocalUtil.setTraceId("o98iuj76yhe3");//复用核心线程,未使用代理executorService.submit(new Runnable() {public void run() {System.out.println("未使用代理:"+ThreadLocalUtil.getUserId());System.out.println("未使用代理:"+ThreadLocalUtil.getTraceId());}});//复用核心线程,Runnable使用代理executorService.submit((Runnable) new TaskProxy(new Runnable() {public void run() {System.out.println("使用代理Runnable:"+ThreadLocalUtil.getUserId());System.out.println("使用代理Runnable:"+ThreadLocalUtil.getTraceId());}}));//复用核心线程,Callable使用代理executorService.submit((Callable) new TaskProxy<String>(new Callable() {public 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的数据传递问题,
大家可以根据自己的需要选择不同的方案。
