一、前言
日常工作中,经常使用ThreadLocal来避免线程并发问题,每个线程访问自己的本地变量,没有竞争,没有锁,非常高效。现在有一个业务场景,需要创建一些子线程来执行任务,父线程中设置了ThreadLocal的值,想在子线程中获取,能获取到吗?答案是:不能。
了解ThreadLocal的原理,这个问题就很弱智,用脚后跟想,父线程中set,那么这个存放值的ThreadLocalMap就在父线程内,子线程的threadLocals是个null,怎么可能从子线程 get 到父线程 set 的值呢?
但是需求就要这样,该如何实现?将父线程的ThreadLocalMap复制一份给子线程?没错,java官方也是这么想的!
二、InheritableThreadLocal
1、使用方式
java 官方提供了一个类InheritableThreadLocal,使用方式上和ThreadLocal完全一样,就是类名不一样。将ThreadLocal替换为InheritableThreadLocal,就可以从子线程get到父线程set的值了。
2、继承关系
InheritableThreadLocal是如何做到的呢?源码底下见真知:
package java.lang;/** * @author Josh Bloch and Doug Lea * @see ThreadLocal * @since 1.2 */public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) { return parentValue; }
ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; }
void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); }}
复制代码
InheritableThreadLocal继承自ThreadLocal,重写了三个方法childValue、getMap、createMap,用到Thread的一个变量inheritableThreadLocals。那就是InheritableThreadLocal初始化的ThreadLocalMap赋值给t.inheritableThreadLocals,set和get也是操作t.inheritableThreadLocals。
public class Thread implements Runnable { ... ... ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; ... ...}
复制代码
3、复制原理
那是如何将父线程的 map 复制给子线程的呢?
追溯到Thread初始化,会调用一个init(),init初始化的东西较多,直接看重点:
真相了,创建子线程时,默认inheritThreadLocals=true,父线程即当前线程的inheritableThreadLocals!=null,则将父线程的inheritableThreadLocals复制给子线程。
// java.lang.ThreadLocal#createInheritedMapstatic ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap);}
复制代码
//java.lang.ThreadLocal.ThreadLocalMap#ThreadLocalMapprivate 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) { // InheritableThreadLocal重写了childValue 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++; } } }}
复制代码
三、childValue 的用意
ThreadLocal中childValue没有给出具体实现,而在InheritableThreadLocal中也只是简单实现了下。
//java.lang.ThreadLocal#childValueT childValue(T parentValue) { throw new UnsupportedOperationException();}
复制代码
//java.lang.InheritableThreadLocal#childValueprotected T childValue(T parentValue) { return parentValue;}
复制代码
从父线程复制ThreaLocalMap到子线程时,值从childValue函数过了一遍再赋值给Entry,是何意图?关键是InheritableThreadLocal也没做什么,但是不难猜出,ThreadLocal留了一个childValue就是让InheritableThreadLocal实现的,虽然InheritableThreadLocal没做什么,但是使用者可以继承InheritableThreadLocal重写childValue,对value做特殊处理。为什么可能要对value做特殊处理呢?
比如,设置的值是一个自定义的引用类型,那么从父线程复制到多个子线程的值就存在并发问题(值传递,地址值是共享的),所以复制的时候要保证复制给每个子线程的地址值不一样,继承InheritableThreadLocal实现childValue的深拷贝,定制化一个自己的InheritableThreadLocal:
public class MyInheritableThreadLocal<T> extends InheritableThreadLocal<T>{ protected T childValue(T parentValue) { System.out.println("MyInheritableThreadLocal。。。"); // 深拷贝 Gson gson = new Gson(); String s = gson.toJson(parentValue); return (T)gson.fromJson(s, parentValue.getClass()); }}
复制代码
public class InheritableThreadLocalTest { public static void main(String[] args) throws InterruptedException { InheritableThreadLocal<Stu> itl = new MyInheritableThreadLocal<Stu>(); itl.set(new Stu()); System.out.println("父线程:" + itl.get().toString()); Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("子线程1:" + itl.get().toString()); } }); thread1.start(); Thread thread2 = new Thread(new Runnable() { @Override public void run() { System.out.println("子线程2:" + itl.get().toString()); } }); thread2.start(); }
static class Stu { private String name = "xxx"; }}
// 控制台打印父线程:com.stefan.DailyTest.InheritableThreadLocalTest$Stu@49476842MyInheritableThreadLocal。。。子线程1:com.stefan.DailyTest.InheritableThreadLocalTest$Stu@7446b2acMyInheritableThreadLocal。。。子线程2:com.stefan.DailyTest.InheritableThreadLocalTest$Stu@75f4c190
复制代码
四、总结
InheritableThreadLocal可以实现子线程获取父线程的本地变量。
子线程初始化时,若父线程(当前线程)的本地变量inheritableThreadLocals不为 null,则复制给子线程。
ThreadLocal留个childValue的用意,就是让InheritableThreadLocal实现,并且可以让客户端自定义重写childValue对从父线程复制到子线程的值做特殊处理。
若父线程使用InheritableThreadLocal设置了自定义引用类型的值,复制给子线程时存在并发问题,需要自行实现childValue的深拷贝。
抛个问题:
如果使用线程池创建子线程,子线程只会初始化一次,父线程中使用InheritableThreadLocal设置值,因为复制机制是在线程初始化的时候,那么父线程只有在线程池初始化子线程时同步复制一次数据,后续父线程再修改值,就无法同步更新到线程池中的子线程了,这该怎么办呢?
只要在每次提交任务时复制就可以了,这就要对线程池以及InheritableThreadLocal做一些定制化处理,让复制机制放在每次提交任务的时候,阿里有一个开源项目给出了解决方案https://github.com/alibaba/transmittable-thread-local,后续可深入了解其实现原理。
PS: 如若文章中有错误理解,欢迎批评指正,同时非常期待你的评论、点赞和收藏。我是徐同学,愿与你共同进步!
评论