概述
我們知道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 可以解決此問題,感興趣的老鐵們可以去了解一下。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多點贊支持。