我的原則:先會用再說,內部慢慢來。
學以致用,根據場景學源碼
文章目錄
- 一、前言
- 二、架構
- 三、實戰 demo
- 3.1 ThreadLocal 無法傳遞線程內變量
- 3.2 實現數值的複製傳遞
- 3.3 變量在父線程的修改,不影響子線程(數值傳遞)
- 3.4 變量在子線程的修改,不影響父線程(數值傳遞)
- 3.5 變量在父線程的修改,不影響子線程(引用傳遞重新New)
- 3.6 變量在子線程的修改,不影響父線程(引用傳遞重新New)
- 3.7 變量在父線程的修改,影響了子線程(改了指針指向的對象內的內容)
- 3.8 變量在子線程的修改,影響了父線程(改了指針指向的對象內的內容)
- 3.9 變量若一開始父線程沒有初始化,那麼後續的 set 對子線程沒作用。
- 四、InheritableThreadLocal 源碼剖析
- 4.1 類 InheritableThreadLocal
- 4.2 getMap 方法 (getter)
- 4.3 createMap 方法 (Setter)
- 4.4 getMap 與 createMap 總結
- 4.5 Thread 類
- 4.6 childValue 方法
- 五、番外篇
一、前言
1.1 基礎
- 必須先看下【線程】ThreadLocal 剖析 (十四)
- 有時間再看下 【線程】ThreadLocal 內存泄漏問題(十五)
1.2 InheritableThreadLocal 能幹嘛?
- 能讓子線程讀取到父線程的變量。(可以用來監控 trace 方法鏈)
二、架構
2.1 代碼架構
2.2 正常初始化子進程
- 此圖片有助於理解引用傳遞和值傳遞
=== 點擊查看top目錄 ===
三、實戰 demo
- 看上圖可以理解下面所有demo
3.1 ThreadLocal 無法傳遞線程內變量
public class _22_01_TestInheritableThreadLocal {
// 1.通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> balance = new ThreadLocal();
// InheritableThreadLocal.withInitial(() -> 100); // 假設初始賬戶有100塊錢
public static void main(String[] args) throws Exception {
balance.set(20);
new Thread(new TaskDemo(), "A").start();
// 讓子線程先走
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
}
private static class TaskDemo implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
}
}
}
- 輸出:
A --> balance[null]
main --> balance[20]
- 結論:ThreadLocal 無法傳遞線程內變量
3.2 實現數值的複製傳遞
- 代碼:
public class _22_02_TestInheritableThreadLocal {
// 1.通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> balance = new InheritableThreadLocal();
// InheritableThreadLocal.withInitial(() -> 100); // 假設初始賬戶有100塊錢
public static void main(String[] args) throws Exception {
balance.set(20);
new Thread(new TaskDemo(), "A").start();
// 讓子線程先走
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
}
private static class TaskDemo implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
}
}
}
- 輸出:
A --> balance[20]
main --> balance[20]
- 結論:父線程定義的線程內局部變量已經被子線程取到了。
3.3 變量在父線程的修改,不影響子線程(數值傳遞)
- 代碼:
public class _22_03_TestInheritableThreadLocal {
// 加個lock 控制流程順序
private static ReentrantLock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
// 1.通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> balance = new InheritableThreadLocal();
public static void main(String[] args) throws Exception {
// 1. 設置 balance
balance.set(20);
new TaskDemo().start();
// sleep 讓子線程先走,看下效果
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
lock.lock();
// 2. 修改 balance 的值,看下子線程的效果
balance.set(40);
System.out.println(Thread.currentThread().getName() + " set New balance ===");
condition.signal();
lock.unlock();
// sleep 讓子線程先走,看下效果
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
}
private static class TaskDemo extends Thread {
public void run() {
// 看下父線程的變量能否繼承襲來
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
try {
lock.lock();
condition.await(); // 注意 await 本身會釋放 lock
// 看下父線程的修改對子類的影響
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
- 輸出:
Thread-0 --> balance[20]
main --> balance[20]
main set New balance ===
Thread-0 --> balance[20]
main --> balance[40]
- 結論:
- 看輸出父線程修改了變量的值之後,子線程仍然輸出了老值。
- 若一開始父線程沒有初始化,那麼後續的 set 對子線程沒影響。詳看情景 3.9
3.4 變量在子線程的修改,不影響父線程(數值傳遞)
- 這個情景跟上面 3.3 一樣,不做冗餘介紹。
3.5 變量在父線程的修改,不影響子線程(引用傳遞重新New)
- 代碼
public class _22_04_TestInheritableThreadLocal {
// 加個lock 控制流程順序
private static ReentrantLock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
// 1.通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Wallet> walletThreadLocal = new InheritableThreadLocal();
public static void main(String[] args) throws Exception {
// 1. 設置 balance
walletThreadLocal.set(new Wallet(20));
// walletThreadLocal.get().setBalance(40);
new Thread(new TaskDemo(),"A").start();
// sleep 讓子線程先走,看下效果
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + walletThreadLocal.get());
lock.lock();
// 2. 修改 balance 的值,看下子線程的效果
walletThreadLocal.set(new Wallet(40));
System.out.println(Thread.currentThread().getName() + " set New balance ===");
condition.signal();
lock.unlock();
// sleep 讓子線程先走,看下效果
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + walletThreadLocal.get());
}
private static class TaskDemo implements Runnable {
public void run() {
// 看下父線程的變量能否繼承襲來
System.out.println(Thread.currentThread().getName() + walletThreadLocal.get());
try {
lock.lock();
condition.await(); // 注意 await 本身會釋放 lock
// 看下父線程的修改對子類的影響
System.out.println(Thread.currentThread().getName() + walletThreadLocal.get());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
@Data
@AllArgsConstructor
private static class Wallet{
private int balance;
@Override
public String toString(){
return "Wallet.balance = [" + balance + "]";
}
}
}
- 輸出:
AWallet.balance = [20]
mainWallet.balance = [20]
main set New balance ===
AWallet.balance = [20]
mainWallet.balance = [40]
- 結論:
- 父類指針指向的對象變了。子類的依舊沒變。所以改了跟他沒關係。
3.6 變量在子線程的修改,不影響父線程(引用傳遞重新New)
- 同上 3.4
3.7 變量在父線程的修改,影響了子線程(改了指針指向的對象內的內容)
- 把 情景 3.5 的代碼改一段,你可以看到效果
walletThreadLocal.set(new Wallet(40));
改成
walletThreadLocal.get().setBalance(40);
輸出:
AWallet.balance = [20]
mainWallet.balance = [20]
main set New balance ===
AWallet.balance = [40]
mainWallet.balance = [40]
- 結論:
- 數值已經被改了。
- 因爲父線程和子線程指向的是同一個對象。你把對象裏面的東西給改了。
3.8 變量在子線程的修改,影響了父線程(改了指針指向的對象內的內容)
- 同上 3.7
3.9 變量若一開始父線程沒有初始化,那麼後續的 set 對子線程沒作用。
- 代碼
// 3.9 變量若一開始父線程沒有初始化,那麼後續的 set 對子線程沒影響。
public class _22_06_TestInheritableThreadLocal {
// 加個lock 控制流程順序
private static ReentrantLock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
// 1.通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> balance = new InheritableThreadLocal();
public static void main(String[] args) throws Exception {
// 1. 設置 balance
new TaskDemo().start();
// sleep 讓子線程先走,看下效果
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
lock.lock();
// 2. 修改 balance 的值,看下子線程的效果
balance.set(40);
System.out.println(Thread.currentThread().getName() + " set New balance ===");
condition.signal();
lock.unlock();
// sleep 讓子線程先走,看下效果
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
}
private static class TaskDemo extends Thread {
public void run() {
// 看下父線程的變量能否繼承襲來
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
try {
lock.lock();
condition.await(); // 注意 await 本身會釋放 lock
// 看下父線程的修改對子類的影響
System.out.println(Thread.currentThread().getName() + " --> balance["
+ balance.get() + "]");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
- 輸出:
Thread-0 --> balance[null]
main --> balance[null]
main set New balance ===
Thread-0 --> balance[null]
main --> balance[40]
- 結論:
- 變量若一開始父線程沒有初始化,那麼後續的 set 對子線程沒影響。
- 這個實驗的目的和情景三 驗證的結論一致。
四、InheritableThreadLocal 源碼剖析
4.1 類 InheritableThreadLocal
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
protected T childValue(T parentValue) {
return parentValue;
}
}
從 類UML圖 可以得知:
- 重寫類父類 ThreadLocal 的兩個方法 getMap + createMap
- 實現了父類ThreadLocal的 childValue方法(父類的實現是空的)
4.2 getMap 方法 (getter)
- java.lang.InheritableThreadLocal#getMap 方法
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
4.2.1 與重載之前的區別
- 重載之前的 ThreadLocal.getMap 方法
- 區別:ThreadLocal返回的是 t.threadLocals,重載後 InheritableThreadLocal 返回的是 t.inheritableThreadLocals。
4.2.2 查看上游
- MAC+IDEA 直接Option + F7 查看方法在哪裏使用。
- 總共有4個地方使用到了這個方法:
- java.lang.ThreadLocal#get 方法
- java.lang.ThreadLocal#setInitialValue 方法
- java.lang.ThreadLocal#set 方法
- java.lang.ThreadLocal#remove 方法
4.3 createMap 方法 (Setter)
- java.lang.InheritableThreadLocal#createMap 方法
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
4.3.1 ThreadLocalMap 構造方法
- java.lang.ThreadLocal.ThreadLocalMap#ThreadLocalMap(java.lang.ThreadLocal.ThreadLocalMap) 方法
/**
* 構建一個包含所有parentMap中Inheritable ThreadLocals的ThreadLocalMap
* 該函數只被 createInheritedMap() 調用.
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
// 淺複製,讓子線程指向了原本父類內部的指向的 Entry表
// 臨時表
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
// ThreadLocalMap 使用 Entry[] table 存儲ThreadLocal
table = new Entry[len];
//// 逐一複製 parentMap 的記錄
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
// e != null,說明不是髒數據。
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
// 調用子線程複寫的 childValue 方法,處理 value,這個地方僅僅是淺複製
Object value = key.childValue(e.value);
// 生成新的 Entry
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
4.3.1.1 Object value = key.childValue(e.value);
- 絕不是網上有些人說的,只是爲了減輕閱讀代碼的難度。而是留給後續子類你可以靈活處理,比如 InheritableThreadLocal 只是簡單的淺複製,你完全可以繼續覆寫該方法改成深負責,或者你對數值進行處理,比如+1或者-1處理。或者可以把 traceId 加入前綴或者後綴
4.3.2 與重載之前的區別
- 重載之前的 ThreadLocal#createMap 方法
- 區別:ThreadLocal#createMap 是初始化 t.threadLocals ,重載後 InheritableThreadLocal#createMap 是初始化 t.inheritableThreadLocals 。
4.3.3 查看上游
- MAC+IDEA 直接Option + F7 查看方法在哪裏使用。
- 總共有2個地方使用到了這個方法:
4.4 getMap 與 createMap 總結
- 一個是 getter ,一個是 setter
- override 之前操作的是 t.threadLocals ,之後是操作 t.inheritableThreadLocals (Thread中一個必定爲null)
- 看下 Thread.class 使你眼前一亮
4.5 Thread 類
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
...
}
4.6 childValue 方法
-
MAC+IDEA 直接Option + F7 查看方法在哪裏使用。
=== 點擊查看top目錄 === -
繼續MAC+IDEA 直接Option + F7 查看方法在哪裏使用。
=== 點擊查看top目錄 ===
4.6.2 ThreadLocal#createInheritedMap 方法
- java.lang.ThreadLocal#createInheritedMap 方法
- 創建 InheritableThreadLocal 的工廠方法
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
- 繼續MAC+IDEA 直接Option + F7 查看方法在哪裏使用。
4.6.3 Thread#init 方法
- java.lang.Thread#init 方法(注意:這個地方跑到了 Thread類來了)
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
4.6.3.1 注意:parent.inheritableThreadLocals != null
注意: parent.inheritableThreadLocals != null ,這個前提是 父線程已經 set 了,也就是場景9強調的情況:變量若一開始父線程沒有初始化,那麼後續的 set 對子線程沒作用。
- 繼續MAC+IDEA 直接Option + F7 查看方法在哪裏使用。
- 不斷查上去,是第一個
- java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long)
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
再上去,我們看到了是 Thread類的初始化。
4.6.4 Thread 初始化
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
- 查看 Thread.class
五、番外篇
上一章節:【線程】ThreadGroup 實戰與剖析 (十七)
上一章節:【線程】ThreadLocal 內存泄漏問題(十五)