vlambda博客
学习文章列表

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() { @Override public 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(); }
@Override public void run() { restoreThreadLocal(); this.runnable.run(); clearThreadLocal(); }
@Override 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() { @Override public void run() { System.out.println("预热,产生核心线程"); } }); } public static void main(String[] args) { //-----主线程绑定数据---- ThreadLocalUtil.setUserId("1001"); ThreadLocalUtil.setTraceId("o98iuj76yhe3");
//复用核心线程,未使用代理 executorService.submit(new Runnable() { @Override public void run() { System.out.println("未使用代理:"+ThreadLocalUtil.getUserId()); System.out.println("未使用代理:"+ThreadLocalUtil.getTraceId()); } });
//复用核心线程,Runnable使用代理 executorService.submit((Runnable) new TaskProxy(new Runnable() { @Override public void run() { System.out.println("使用代理Runnable:"+ThreadLocalUtil.getUserId()); System.out.println("使用代理Runnable:"+ThreadLocalUtil.getTraceId()); } }));
//复用核心线程,Callable使用代理 executorService.submit((Callable) new TaskProxy<String>(new Callable() { @Override 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的数据传递问题,

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