InheritableThreadLocal相比於ThreadLocal,支持子線程繼承父線程的數據,一起來看看它的實現原理吧!
1 從ThreadLocal說起
在前面ThreadLocal文章中:ThreadLocal源碼深度解析與應用案例。我們知道每個線程都具有一個threadLocals屬性,用於存放線程本地變量。
當然如果我們眼睛夠細,在我們threadLocals屬性下面還可以發現一個同類型的屬性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;
實際上,該屬性用於實現線程本地變量的繼承性:子線程創建時可以從父線程繼承線程本地變量。inheritableThreadLocals主要存儲可自動向子線程中傳遞的ThreadLocalMap,他也對應着一個類:InheritableThreadLocal。
在hreadLocal的文章的第一個案例的中,同一個ThreadLocal 變量在父線程(main)中被設置值後, 在子線程(child)中是獲取不到的。這應該是正常現象,因爲在子線程thread 裏面調用get 方法時當前線程爲thread 線程,而這裏調用set方法設置線程變量的是main 線程,兩者是不同的線程,自然子線程訪問時返回null。
但是可能有些業務,需要子線程獲取到父線程的數據,那麼有沒有辦法讓子線程能訪問到父線程中的值? 答案是有,那就是InheritableThreadLocal。
2 InheritableThreadLocal的原理
2.1 InheritableThreadLocal的源碼
public class InheritableThreadLocal< T >
extends ThreadLocal< T >
InheritableThreadLocal繼承自ThreadLocal , 保存線程本地變量的集合也是使用的ThreadLocalMap。但是其增加了一個特性,就是讓子線程可以訪問在父線程中設置的本地變量。
InheritableThreadLocal類的源碼很少,重寫了ThreadLocal的3個函數,也是該類的源全部源碼:
/**
* 1
* InheritableThreadLocal重寫的實現,當調用get/set 方法獲取當前線程內部的map 變量時,獲取
* 的是inheritableThreadLocals 而不再是threadLocals。
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
/**
* ThreadLocal的實現
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 操作InheritableThreadLocal對象時,第一次調用set/get方法時
* 創建的是當前線程的inheritableThreadLocals 變量的實例而不再是threadLocals變量實例。
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
/**
* ThreadLocal的實現
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
/**
* 3
* 該函數在父線程中創建子線程並向子線程複製inheritableThreadLocal變量時使用。
* 在ThreadLocal中實現爲拋出UnsupportedOperationException異常
*/
protected T childValue(T parentValue) {
return parentValue;
}
/**
* ThreadLocal的實現
*/
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
2.2 傳遞數據的原理
傳遞數據的原理還要從創建線程開始說起!實際上子線程的很多屬性如果自己沒有指定的話,都會繼承父線程的屬性,比如線程組、守護/用戶線程屬性、線程的上下文對象等。
/**
* Thread類的構造器
*
* @param target 線程任務
* @param name 線程名字
*/
public Thread(Runnable target, String name) {
//內部調用init方法
init(null, target, name, 0);
}
/**
* Thread類的init方法,用於初始化線程的參數
*
* @param g 線程組
* @param target 線程任務
* @param name 線程名字
* @param stackSize 當前線程棧大小
*/
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
//調用另外一個init方法
init(g, target, name, stackSize, null, true);
}
/**
* Thread類的另一個init方法
*
* @param g 線程組
* @param target 線程任務
* @param name 線程名字
* @param stackSize 當前線程棧大小
* @param acc 訪問控制權限
* @param inheritThreadLocals 如果爲true,則從構造線程(父線程)中繼承線程局部變量的初始值
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//設置名字
this.name = name;
//獲取當前線程,即父線程(main)
Thread parent = currentThread();
//如果inheritThreadLocals爲true並且父線程的inheritableThreadLocals 變量不爲null
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
//4 則將父線程inheritableThreadLocals的數據傳遞至子線程的inheritableThreadLocals屬性中
//注意這裏時傳遞的引用值,對於引用類型的值(除了string)相當於淺拷貝,實際上他們還是同一個對象
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
//…………
}
/**
* 4
* ThreadLocal類的方法
* 父線程的inheritableThreadLocals包裝到子線程的inheritableThreadLocals中
*
* @param parentMap 父線程的inheritableThreadLocals
* @return ThreadLocalMap
*/
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
//5 新建ThreadLocalMap,包含指定的ThreadLocalMap的所有數據
return new ThreadLocalMap(parentMap);
}
/**
* ThreadLocalMap類的構造函數,包含指定的ThreadLocalMap的所有數據
*
* @param parentMap 指定的ThreadLocalMap對象
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
//獲取table數組
ThreadLocalMap.Entry[] parentTable = parentMap.table;
//獲取長度
int len = parentTable.length;
//設置擴容閥值
setThreshold(len);
//新建table
table = new ThreadLocalMap.Entry[len];
for (int j = 0; j < len; j++) {
ThreadLocalMap.Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
//這裏終於調用到了重寫的childValue方法
//用於把父線程的inheritableThreadLocals屬性的數據複製到新的ThreadLocalMap對象中
Object value = key.childValue(e.value);
ThreadLocalMap.Entry c = new ThreadLocalMap.Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
3 總結和案例
InheritableThreadLocal 類重寫了三個方法。createMap將創建的ThreadLocalMap賦值給具體線程的inheritableThreadLocals變量。getMap將返回具體線程的inheritableThreadLocals變量,從而讓本地變量保存到了inheritableThreadLocals變量裏面,那麼線程在通過InheritableThreadLocal 類實例的set 或者get 方法設置變量時,就會創建當前線程的inheritableThreadLocals 變量。
當在父線程中創建子線程時,構造函數會把父線程中inheritableThreadLocals變量裏面的本地變量複製一份(淺克隆)保存到子線程的inheritableThreadLocals 變量裏面。
InheritableThreadLocal傳遞數據的案例:
public class InheritableThreadLocalTest {
static InheritableThreadLocal<InheritableThreadLocalTest> threadLocal = new InheritableThreadLocal<>();
public static void set() {
threadLocal.set(new InheritableThreadLocalTest());
}
public static InheritableThreadLocalTest get() {
return threadLocal.get();
}
public static void main(String[] args) throws InterruptedException {
System.out.println("主線程中嘗試獲取值====>" + get());
System.out.println("主線程中設置值====>");
set();
//主線程中嘗試獲取值
System.out.println("主線程中再次嘗試獲取值====>" + get());
System.out.println();
System.out.println("開啓一條子線程====>");
Thread thread = new Thread(new Th1(), "child");
thread.start();
//主線程等待子線程執行完畢
thread.join();
System.out.println();
System.out.println("等待子線程執行完畢,主線程中再次嘗試獲取值====>" + get());
System.out.println("獲取到的還是父線程原來的值,即父可以傳遞數據給子,子不能傳遞數據給父");
}
static class Th1 implements Runnable {
@Override
public void run() {
System.out.println("子線程中嘗試獲取值====>" + get());
System.out.println("能夠直接獲取到,說明父線程的數據傳遞給了子線程,並且獲取到的對象和父類獲取到的對象還是同一個,即淺拷貝");
System.out.println("子線程中設置值====>");
set();
System.out.println("子線程中再次嘗試獲取值====>" + get());
System.out.println("此時獲取到的,是自己設置的值");
System.out.println();
}
}
@Override
public String toString() {
return this.hashCode() + "";
}
}
結果:
主線程中嘗試獲取值====>null
主線程中設置值====>
主線程中再次嘗試獲取值====>1878246837
開啓一條子線程====>
子線程中嘗試獲取值====>1878246837
能夠直接獲取到,說明父線程的數據傳遞給了子線程,並且獲取到的對象和父類獲取到的對象還是同一個,即淺拷貝
子線程中設置值====>
子線程中再次嘗試獲取值====>185894025
此時獲取到的,是自己設置的值
等待子線程執行完畢,主線程中再次嘗試獲取值====>1878246837
獲取到的還是父線程原來的值,即父可以傳遞數據給子,子不能傳遞數據給父
可以看出,子線程可以正常獲取到父線程本地變量的值。InheritableThreadLocal常常用在調用鏈路追蹤上。
如果有什麼不懂或者需要交流,可以留言。另外希望點贊、收藏、關注,我將不間斷更新各種Java學習博客!