InheritableThreadLocal父子线程变量共享实现原理
概述
我们知道ThreadLocal 的设计初衷是为了解决多线程并发导致的线程安全问题,向每一个线程提供一个自己的变量副本,实现变量隔离。那如何在不同线程之间共享变量呢?InheritableThreadLocal为解决此问题而生,使用她可以实现父子线程访问ThreadLocal的值。
实现变量传递的关键是在类Thread中定义的本地变量inheritableThreadLocals:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
案例分析
下面先提供一个案例,说明InheritableThreadLocal可以实现父子线程间传递变量。
/**
* 线程间共享变量
*
* @author Wiener
* @date 2020/10/27
*/
public class ShareArgsProblem {
private static void shareArgs() {
//主线程中赋值
ThreadLocal<String> threadLocal = new ThreadLocal<>();
final ThreadLocal itl = new InheritableThreadLocal();
itl.set("帅得一匹!");
threadLocal.set("在 ThreadLocal 中存放了值");
System.out.println(String.format("① 当前线程名称:%s", Thread.currentThread().getName()));
Thread t = new Thread() {
@Override
public void run() {
super.run();
//子线程中读取
System.out.println(String.format("楼兰的胡杨帅么?%s", itl.get()));
System.out.println(String.format("拿不到ThreadLocal 存储的值:%s", threadLocal.get()));
System.out.println(String.format("② 当前线程名称: %s", Thread.currentThread().getName()));
}
};
t.start();
}
public static void main(String[] args) {
shareArgs();
}
}
执行main函数,输出结果如下:
在上面的测试用例中,开启两个线程——主线程和子线程t,主线程中创建了两个变量和一个新的线程t,并尝试在线程t中获取本地变量的值。从输出结果可以看得出来,我们拿不到ThreadLocal类型变量 threadLocal 的值,但是,能够准确拿到 InheritableThreadLocal 类型的变量 itl的值。下面分析为什么可以读取出来InheritableThreadLocal 类型的变量的值。
InheritableThreadLocal共享变量原理分析
先让我们看看InheritableThreadLocal的寥寥数行源码:
/**
* This class extends {@code ThreadLocal} to provide inheritance of values
* from parent thread to child thread: when a child thread is created, the
* child receives initial values for all inheritable thread-local variables
* for which the parent has values. Normally the child's values will be
* identical to the parent's; however, the child's value can be made an
* arbitrary function of the parent's by overriding the {@code childValue}
* method in this class.
*
* <p>Inheritable thread-local variables are used in preference to
* ordinary thread-local variables when the per-thread-attribute being
* maintained in the variable (e.g., User ID, Transaction ID) must be
* automatically transmitted to any child threads that are created.
* @author Josh Bloch and Doug Lea
*/
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,作为子类,它重写了如上所述的三个方法。从上述注释得知,父线程thread-local 变量会被传递至子线程中,下面看看如何传递的。
public Thread() {//创建新线程时调用的构造方法
this(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
this(group, target, name, stackSize, null, true);
}
/**
* Initializes a Thread.
*/
private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
Thread parent = currentThread();
...
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);//①
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
this.tid = nextThreadID();
}
跟着进去几层后,发现在①处给inheritableThreadLocals赋值,继续看 ThreadLocal.createInheritedMap的返回值是什么:
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
/**
* Construct a new map including all Inheritable ThreadLocals
* from given parent map. Called only by createInheritedMap.
*
* @param parentMap the map associated with parent thread.
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table; //父线程的table
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];//当前线程的table
//循环取父线程的值到当前线程
for (Entry e : parentTable) {
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
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可以得知,首先拿到父线程中的键值对表table,然后循环遍历parentTable,把父线程中的这些值通过值传递复制到子线程的ThreadLocalMap对象中,此时子线程的成员变量 inheritableThreadLocals中就有了值。其实,ThreadLocalMap的注释已经明确告诉我们,这里将把父线程的可遗传本地变量(Inheritable ThreadLocals)复制到当前线程中。这种模式,有点类似于ClassLoader的 loadClass() 的机制。
通过查看Object value = key.childValue(e.value)的实现方式,我们知道这里是值传递。
小结
Thread对象通过成员变量ThreadLocal.ThreadLocalMap inheritableThreadLocals维护从父线程(创建该线程的线程)继承而来的数据。原理就是在父线程创建子线程时,如果父线程的inheritableThreadLocals不是null,则ThreadLocalMap中的构造函数会复制一份保存到子线程的inheritableThreadLocals变量中。
由于本地变量在父子传递过程中通过值传递,所以即使父线程的本地变量发生了改变,子线程的本地变量依旧是创建线程时初始化的值。在实际的应用场景里,基本都是使用线程池来进行多线程编程,因线程池复用线程,而非每次创建新的线程,所以如果更新父线程InheritableThreadLocal 的值,被复用的子线程中的InheritableThreadLocal变量值不会被更新,从而导致数据异常。阿里开源的TransmittableThreadLocal 可以解决此问题,感兴趣的老铁们可以去了解一下。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多点赞支持。