ThreadLocal對外提供的API如下:
-
public T get()
從線程上下文環境中獲取設置的值。 -
public void set(T value)
將值存儲到線程上下文環境中,供后續使用。 -
public void remove()
清除線程本地上下文環境。
上述API使用簡單,關鍵是要理解ThreadLocal的內部存儲結果。
1.1 ThreadLocal存儲
圖的幾個關鍵點如下:
-
數據存儲
當線程調用threadLocal對象的set(Object value)方法時,數據並不是存儲在ThreadLocal對象中,而是存儲在Thread對象中,這也是ThreadLocal的由來,具體存儲在線程對象的threadLocals屬性中,其類型為ThreadLocal.ThreadLocalMap。 -
ThreadLocal.ThreadLocalMap,Map結構,即鍵值對,鍵為threadLocal對象,值為需要存儲到線程上下文的值(threadLocal#set)方法的參數。
1.2 源碼分析ThreadLocal
1.2.1 源碼分析get
1public T get() {
2 Thread t = Thread.currentThread(); // @1
3 ThreadLocalMap map = getMap(t); // @2
4 if (map != null) { // @3
5 ThreadLocalMap.Entry e = map.getEntry(this);
6 if (e != null) {
7 @SuppressWarnings("unchecked")
8 T result = (T)e.value;
9 return result;
10 }
11 }
12 return setInitialValue(); // @4
13}
代碼@1:獲取當前線程。
代碼@2:獲取線程的threadLocals屬性,在上圖中已展示其存儲結構。
代碼@3:如果線程對象的threadLocals屬性不為空,則從該Map結構中,用threadLocal對象為鍵去查找值,如果能找到,則返回其value值,否則執行代碼@4。
代碼@4:如果線程對象的threadLocals屬性為空,或未從threadLocals中找到對應的鍵值對,則調用該方法執行初始化。
ThreadLocal#setInitialValue
1private T setInitialValue() {
2 T value = initialValue(); // @1
3 Thread t = Thread.currentThread(); // @2
4 ThreadLocalMap map = getMap(t); // @3
5 if (map != null) //@4
6 map.set(this, value);
7 else
8 createMap(t, value); // @5
9 return value;
10}
代碼@1:調用initialValue()獲取默認初始化值,該方法默認返回null,子類可以重寫,實現線程本地變量的初始化。
代碼@2:獲取當前線程。
代碼@3:獲取該線程對象的threadLocals屬性。
代碼@4:如果不為空,則將threadLocal:value存入線程對象的threadLocals屬性中。
代碼@5:否則初始化線程對象的threadLocals,然后將threadLocal:value鍵值對存入線程對象的threadLocals屬性中。
1.2.2 源碼分析set
1public void set(T value) {
2 Thread t = Thread.currentThread();
3 ThreadLocalMap map = getMap(t);
4 if (map != null)
5 map.set(this, value);
6 else
7 createMap(t, value);
8}
在掌握了get方法實現細節,set方法、remove其實現的邏輯基本一樣,就是對線程對象的threadLocals屬性進行操作(Map結構)。
1.3 ThreadLocal局限性
經過上面的剖析,對ThreadLocal的內部存儲、set、get、remove等實現細節都已做了詳細的解讀,但ThreadLocal無法在父子線程之間傳遞,示例代碼如下:
1public class Service {
2 private static ThreadLocal<Integer> requestIdThreadLocal = new ThreadLocal<>();
3 public static void main(String[] args) {
4 Integer reqId = new Integer(5);
5 Service a = new Service();
6 a.setRequestId(reqId);
7 }
8
9 public void setRequestId(Integer requestId) {
10 requestIdThreadLocal.set(requestId);
11 doBussiness();
12 }
13
14 public void doBussiness() {
15 System.out.println("首先打印requestId:" + requestIdThreadLocal.get());
16 (new Thread(new Runnable() {
17 @Override
18 public void run() {
19 System.out.println("子線程啟動");
20 System.out.println("在子線程中訪問requestId:" + requestIdThreadLocal.get());
21 }
22 })).start();
23 }
24}
運行結果如下:
從結果上來看,在子線程中無法訪問在父線程中設置的本地線程變量,那我們該如何來解決該問題呢?
為了解決該問題,JDK引入了另外一個線程本地變量實現類InheritableThreadLocal,接下來將重點介紹InheritableThreadLocal的實現原理。
InheritableThreadLocal
由於ThreadLocal在父子線程交互中子線程無法訪問到存儲在父線程中的值,無法滿足某些場景的需求,例如鏈路跟蹤,例如如下場景:
為了解決上述問題,JDK引入了InheritableThreadLocal,即子線程可以訪問父線程中的線程本地變量,更嚴謹的說法是子線程可以訪問在創建子線程時父線程當時的本地線程變量,因為其實現原理就是在創建子線程將父線程當前存在的本地線程變量拷貝到子線程的本地線程變量中。
2.1 類圖
從類的繼承層次來看,InheritableThreadLocal只是在ThreadLocal的get、set、remove流程中,重寫了getMap、createMap方法,整體流程與ThreadLocal保持一致,故我們初步來看一下InheritableThreadLocal是如何重寫上述這兩個方法的。
1ThreadLocalMap getMap(Thread t) {
2 return t.inheritableThreadLocals;
3}
4void createMap(Thread t, T firstValue) {
5 t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
6}
從代碼得知,ThreadLocal操作的是Thread對象的threadLocals屬性,而InheritableThreadLocal操作的是Thread對象的inheritableThreadLocals屬性。
溫馨提示:createMap被執行的條件是調用InheritableThreadLocal#get、set時如果線程的inheritableThreadLocals屬性為空時才會被調用。
那問題來了,InheritableThreadLocal是如何繼承自父對象的線程本地變量的呢?
2.2 線程上下文環境如何從父線程傳遞到子線程
這部分的代碼入口為:Thread#init方法
1Thread parent = currentThread(); // @1
2
3// 省略部分代碼
4
5if (inheritThreadLocals && parent.inheritableThreadLocals != null) // @2
6 this.inheritableThreadLocals =
7 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
8/* Stash the specified stack size in case the VM cares */
9this.stackSize = stackSize;
10
11/* Set thread ID */
12tid = nextThreadID();
子線程是通過在父線程中通過調用new Thread()方法來創建子線程,Thread#init方法在Thread的構造方法中被調用。
代碼@1:獲取當前線程對象,即待創建的線程的父線程。
代碼@2:如果父線程的inheritableThreadLocals不為空並且inheritThreadLocals為true(該值默認為true),則使用父線程的inherit本地變量的值來創建子線程的inheritableThreadLocals結構,即將父線程中的本地變量復制到子線程中。
1static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
2 return new ThreadLocalMap(parentMap);
3}
4private ThreadLocalMap(ThreadLocalMap parentMap) {
5 Entry[] parentTable = parentMap.table;
6 int len = parentTable.length;
7 setThreshold(len);
8 table = new Entry[len];
9
10 for (int j = 0; j < len; j++) {
11 Entry e = parentTable[j];
12 if (e != null) {
13 @SuppressWarnings("unchecked")
14 ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
15 if (key != null) {
16 Object value = key.childValue(e.value);
17 Entry c = new Entry(key, value);
18 int h = key.threadLocalHashCode & (len - 1);
19 while (table[h] != null)
20 h = nextIndex(h, len);
21
22 table[h] = c;
23 size++;
24 }
25 }
26 }
27}
上述代碼就不一一分析,類似於Map的復制,只不過其在Hash沖突時,不是使用鏈表結構,而是直接在數組中找下一個為null的槽位。
溫馨提示:子線程默認拷貝父線程的方式是淺拷貝,如果需要使用深拷貝,需要使用自定義ThreadLocal,繼承InheritableThreadLocal並重寫childValue方法。
2.3 驗證InheritableThreadLocal的特性
驗證代碼如下:
1public class Service {
2 private static InheritableThreadLocal<Integer> requestIdThreadLocal = new InheritableThreadLocal<>();
3 public static void main(String[] args) {
4 Integer reqId = new Integer(5);
5 Service a = new Service();
6 a.setRequestId(reqId);
7 }
8
9 public void setRequestId(Integer requestId) {
10 requestIdThreadLocal.set(requestId);
11 doBussiness();
12 }
13
14 public void doBussiness() {
15 System.out.println("首先打印requestId:" + requestIdThreadLocal.get());
16 (new Thread(new Runnable() {
17 @Override
18 public void run() {
19 System.out.println("子線程啟動");
20 System.out.println("在子線程中訪問requestId:" + requestIdThreadLocal.get());
21 }
22 })).start();
23 }
24}
執行結果如下:
符合預期,在子線程中如願訪問到了在主線程中設置的本地環境變量。
2.4 InheritableThreadLocal局限性
InheritableThreadLocal支持子線程訪問在父線程中設置的線程上下文環境的實現原理是在創建子線程時將父線程中的本地變量值復制到子線程,即復制的時機為創建子線程時。
但我們提到並發、多線程就離不開線程池的使用,因為線程池能夠復用線程,減少線程的頻繁創建與銷毀,如果使用InheritableThreadLocal,那么線程池中的線程拷貝的數據來自於第一個提交任務的外部線程,即后面的外部線程向線程池中提交任務時,子線程訪問的本地變量都來源於第一個外部線程,造成線程本地變量混亂,驗證代碼如下:
1import java.util.concurrent.ExecutorService;
2import java.util.concurrent.Executors;
3public class Service {
4 /**
5 * 模擬tomcat線程池
6 */
7 private static ExecutorService tomcatExecutors = Executors.newFixedThreadPool(10);
8 /**
9 * 業務線程池,默認Control中異步任務執行線程池
10 */
11 private static ExecutorService businessExecutors = Executors.newFixedThreadPool(5);
12 /**
13 * 線程上下文環境,模擬在Control這一層,設置環境變量,然后在這里提交一個異步任務,模擬在子線程中,是否可以訪問到剛設置的環境變量值。
14 */
15 private static InheritableThreadLocal<Integer> requestIdThreadLocal = new InheritableThreadLocal<>();
16
17 public static void main(String[] args) {
18
19 for(int i = 0; i < 10; i ++ ) { // 模式10個請求,每個請求執行ControlThread的邏輯,其具體實現就是,先輸出父線程的名稱,
20 // 然后設置本地環境變量,並將父線程名稱傳入到子線程中,在子線程中嘗試獲取在父線程中的設置的環境變量
21 tomcatExecutors.submit(new ControlThread(i));
22 }
23
24 //簡單粗暴的關閉線程池
25 try {
26 Thread.sleep(10000);
27 } catch (InterruptedException e) {
28 e.printStackTrace();
29 }
30 businessExecutors.shutdown();
31 tomcatExecutors.shutdown();
32 }
33
34
35 /**
36 * 模擬Control任務
37 */
38 static class ControlThread implements Runnable {
39 private int i;
40
41 public ControlThread(int i) {
42 this.i = i;
43 }
44 @Override
45 public void run() {
46 System.out.println(Thread.currentThread().getName() + ":" + i);
47 requestIdThreadLocal.set(i);
48 //使用線程池異步處理任務
49 businessExecutors.submit(new BusinessTask(Thread.currentThread().getName()));
50 }
51 }
52
53 /**
54 * 業務任務,主要是模擬在Control控制層,提交任務到線程池執行
55 */
56 static class BusinessTask implements Runnable {
57 private String parentThreadName;
58
59 public BusinessTask(String parentThreadName) {
60 this.parentThreadName = parentThreadName;
61 }
62
63 @Override
64 public void run() {
65 //如果與上面的能對應上來,則說明正確,否則失敗
66 System.out.println("parentThreadName:" + parentThreadName + ":" + requestIdThreadLocal.get());
67 }
68 }
69
70}
執行效果如下:
1pool-1-thread-1:0
2pool-1-thread-2:1
3pool-1-thread-3:2
4pool-1-thread-4:3
5pool-1-thread-5:4
6pool-1-thread-6:5
7pool-1-thread-7:6
8pool-1-thread-8:7
9pool-1-thread-9:8
10pool-1-thread-10:9
11parentThreadName:pool-1-thread-7:6
12parentThreadName:pool-1-thread-4:6
13parentThreadName:pool-1-thread-3:6
14parentThreadName:pool-1-thread-2:6
15parentThreadName:pool-1-thread-1:6
16parentThreadName:pool-1-thread-9:6
17parentThreadName:pool-1-thread-10:6
18parentThreadName:pool-1-thread-8:7
19parentThreadName:pool-1-thread-6:5
20parentThreadName:pool-1-thread-5:4
從這里可以出thread-7、thread-4、thread-3、thread-2、thread-1、thread-9、thread-10獲取的都是6,在子線程中出現出現了線程本地變量混亂的現象,在全鏈路跟蹤與壓測出現這種情況是致命的。
問題:大家通過上面的學習,應該能解釋這個現象?此處可以稍微停下來思考一番。
怎么解決這個問題呢?
TransmittableThreadLocal ”閃亮登場“。
TransmittableThreadLocal
3.1 TransmittableThreadLocal“何許人也”
TransmittableThreadLocal何許人也,它可是阿里巴巴開源的專門解決InheritableThreadLocal的局限性,實現線程本地變量在線程池的執行過程中,能正常的訪問父線程設置的線程變量。實踐是檢驗整理的唯一標准,我們還是以上面的示例來進行驗證,看看TransmittableThreadLocal是否支持上述場景:
首先需要在pom.xml文件中引入如下maven依賴:
1<dependency>
2 <groupId>com.alibaba</groupId>
3 <artifactId>transmittable-thread-local</artifactId>
4 <version>2.10.2</version>
5</dependency>
示例代碼如下:
1public class Service {
2
3 /**
4 * 模擬tomcat線程池
5 */
6 private static ExecutorService tomcatExecutors = Executors.newFixedThreadPool(10);
7
8 /**
9 * 業務線程池,默認Control中異步任務執行線程池
10 */
11 private static ExecutorService businessExecutors = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(4)); // 使用ttl線程池,該框架的使用,請查閱官方文檔。
12
13 /**
14 * 線程上下文環境,模擬在Control這一層,設置環境變量,然后在這里提交一個異步任務,模擬在子線程中,是否可以訪問到剛設置的環境變量值。
15 */
16 private static TransmittableThreadLocal<Integer> requestIdThreadLocal = new TransmittableThreadLocal<>();
17
18// private static InheritableThreadLocal<Integer> requestIdThreadLocal = new InheritableThreadLocal<>();
19
20 public static void main(String[] args) {
21
22 for(int i = 0; i < 10; i ++ ) {
23 tomcatExecutors.submit(new ControlThread(i));
24 }
25
26 //簡單粗暴的關閉線程池
27 try {
28 Thread.sleep(10000);
29 } catch (InterruptedException e) {
30 e.printStackTrace();
31 }
32 businessExecutors.shutdown();
33 tomcatExecutors.shutdown();
34
35 }
36
37
38 /**
39 * 模擬Control任務
40 */
41 static class ControlThread implements Runnable {
42 private int i;
43
44 public ControlThread(int i) {
45 this.i = i;
46 }
47 @Override
48 public void run() {
49 System.out.println(Thread.currentThread().getName() + ":" + i);
50 requestIdThreadLocal.set(i);
51
52 //使用線程池異步處理任務
53
54 businessExecutors.submit(new BusinessTask(Thread.currentThread().getName()));
55
56
57 }
58 }
59
60 /**
61 * 業務任務,主要是模擬在Control控制層,提交任務到線程池執行
62 */
63 static class BusinessTask implements Runnable {
64 private String parentThreadName;
65
66 public BusinessTask(String parentThreadName) {
67 this.parentThreadName = parentThreadName;
68 }
69
70 @Override
71 public void run() {
72 //如果與上面的能對應上來,則說明正確,否則失敗
73 System.out.println("parentThreadName:" + parentThreadName + ":" + requestIdThreadLocal.get());
74 }
75 }
76
77}
其運行結果如下:
1pool-1-thread-10:9
2pool-1-thread-8:7
3pool-1-thread-7:6
4pool-1-thread-9:8
5pool-1-thread-6:5
6pool-1-thread-5:4
7pool-1-thread-4:3
8pool-1-thread-3:2
9pool-1-thread-2:1
10pool-1-thread-1:0
11parentThreadName:pool-1-thread-5:4
12parentThreadName:pool-1-thread-9:8
13parentThreadName:pool-1-thread-3:2
14parentThreadName:pool-1-thread-2:1
15parentThreadName:pool-1-thread-7:6
16parentThreadName:pool-1-thread-8:7
17parentThreadName:pool-1-thread-1:0
18parentThreadName:pool-1-thread-6:5
19parentThreadName:pool-1-thread-10:9
20parentThreadName:pool-1-thread-4:3
執行結果符合預期。那TransmittableThreadLocal是如何實現的呢?
3.2 TransmittableThreadLocal實現原理
從InheritableThreadLocal不支持線程池的根本原因是InheritableThreadLocal是在父線程創建子線程時復制的,由於線程池的復用機制,“子線程”只會復制一次。要支持線程池中能訪問提交任務線程的本地變量,其實只需要在父線程向線程池提交任務時復制父線程的上下環境,那在子線程中就能夠如願訪問到父線程中的本地變量,實現本地環境變量在線程池調用中的透傳,從而為實現鏈路跟蹤打下堅實的基礎,這也就是TransmittableThreadLocal最本質的實現原理。
3.2.1 TransmittableThreadLocal類圖
TransmittableThreadLocal繼承自InheritableThreadLocal,接下來將從set方法為入口,開始探究TransmittableThreadLocal實現原理。
3.2.2 set方法詳解
1public final void set(T value) {
2 super.set(value); // @1
3 // may set null to remove value
4 if (null == value) // @2
5 removeValue();
6 else
7 addValue();
8}
代碼@1:首先調用父類的set方法,將value存入線程本地遍歷,即Thread對象的inheritableThreadLocals中。
代碼@2:如果value為空,則調用removeValue()否則調用addValue。
那接下來重點看看這兩個方法有什么名堂:
1private void addValue() {
2 if (!holder.get().containsKey(this)) { // @1
3 holder.get().put(this, null); // WeakHashMap supports null value.
4 }
5}
6private void removeValue() {
7 holder.get().remove(this);
8}
代碼@1:當前線程在調用threadLocal方法的set方法(即向線程本地遍歷存儲數據時),如果需要設置的值不為null,則調用addValue方法,將當前ThreadLocal存儲到TransmittableThreadLocal的全局靜態變量holder。holder的定義如下:
1private static InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>> holder =
2 new InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>>() {
3 @Override
4 protected Map<TransmittableThreadLocal<?>, ?> initialValue() {
5 return new WeakHashMap<TransmittableThreadLocal<?>, Object>();
6 }
7
8 @Override
9 protected Map<TransmittableThreadLocal<?>, ?> childValue(Map<TransmittableThreadLocal<?>, ?> parentValue) {
10 return new WeakHashMap<TransmittableThreadLocal<?>, Object>(parentValue);
11 }
12 };
從中可以看出,使用了線程本地變量,內部存放的結構為Map, ?>,即該對象緩存了線程執行過程中所有的TransmittableThreadLocal對象,並且其關聯的值不為空。但這樣做有什么用呢?
為了解開這個難題,可能需要大家對ttl這個框架的使用有一定的理解,本文由於篇幅的原因,將不會詳細介紹,如有大家有興趣,可以查閱其官網解其使用:
https://github.com/alibaba/transmittable-thread-local
1ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(4));
2TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();
3parent.set("value-set-in-parent");
4Runnable task = new Task("1");
5Callable call = new Call("2");
6executorService.submit(task);
7executorService.submit(call);
8
9我們從submit為突破口,來嘗試解開holder屬性用途。
10class ExecutorTtlWrapper implements Executor, TtlEnhanced {
11 private final Executor executor;
12
13 ExecutorTtlWrapper(@Nonnull Executor executor) {
14 this.executor = executor;
15 }
16
17 @Override
18 public void execute(@Nonnull Runnable command) {
19 executor.execute(TtlRunnable.get(command)); // @1
20 }
21
22 @Nonnull
23 public Executor unwrap() {
24 return executor;
25 }
26}
在向線程池提交任務時,會使用TtlRunnable對提交任務進行包裝。接下來將重點探討TtlRunnable。
3.2.2 TtlRunnable詳解
3.2.2.1 類圖
下面一一來介紹其核心屬性:
-
AtomicReference capturedRef
“捕獲”的引用,根據下文的解讀,該引用指向的數據結構包含了父線程在執行過程中,通過使用TransmittableThreadLocal存儲的本地線程變量,但這里的觸發時機是向線程池提交任務時捕獲。 -
Runnable runnable
提交到線程池中待執行的業務邏輯。 -
boolean releaseTtlValueReferenceAfterRun
默認為false。
接下來重點看一下其構造方法:
1private TtlRunnable(@Nonnull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
2 this.capturedRef = new AtomicReference<Object>(capture()); // @1
3 this.runnable = runnable;
4 this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
5}
構造方法沒什么特別,重點看一下子線程是如何“捕獲”父線程中已設置的本地線程變量。
1TransmittableThreadLocal$Transmitter#capture
2public static Object capture() {
3 Map<TransmittableThreadLocal<?>, Object> captured = new HashMap<TransmittableThreadLocal<?>, Object>(); // @1
4 for (TransmittableThreadLocal<?> threadLocal : holder.get().keySet()) { // @2
5 captured.put(threadLocal, threadLocal.copyValue()); // @3
6 }
7 return captured;
8}
代碼@1:先創建Map容器,用來存儲父線程的本地線程變量,鍵為在父線程執行過程中使用到的TransmittableThreadLocal線程。
代碼@2:holder.get(),獲取父線程中使用中的ThreadLocal,因為我們從3.2.2節中發現,在當前線程在調用TransmittableThreadLocal的set方法,並且其值不為空的時候,會將TransmittableThreadLocal對象存儲存儲在當前線程的本地變量中。故這里使用holder.get()方法能獲取父線程中已使用的ThreadLocal,並其值不為null。
代碼@3:遍歷父線程已使用的線程本地,將其值存入到captured中,注意默認是淺拷貝,如果需要實現深度拷貝,請重寫TransmittableThreadLocal的copyValue方法。
溫馨提示:從這里看出TransmittableThreadLocal的靜態屬性holder的用處吧,請重點理解holder的屬性類型為:InheritableThreadLocal, ?>>。
在向線程池提交任務時,會先捕獲父線程(提交任務到線程池的線程)中的本地環境變量,接下來重點來看一下其run方法。
3.2.2.2 run方法
1public void run() {
2 Object captured = capturedRef.get();
3 if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
4 throw new IllegalStateException("TTL value reference is released after run!");
5 }
6
7 Object backup = replay(captured); // @1
8 try {
9 runnable.run(); // @2
10 } finally {
11 restore(backup); // @3
12 }
13}
代碼@1:"重放"父線程的本地環境變量,即使用從父線程中捕獲過來的上下文環境,在子線程中重新執行一遍,並返回原先存在與子線程中的上下文環境變量。
代碼@2:執行業務邏輯。
代碼@3:恢復線程池中當前執行任務的線程的上下文環境,即代碼@1,會直接繼承父線程中的上下文環境,但會將原先存在該線程的線程上下文環境進行備份,在任務執行完后通過執行restore方法進行恢復。
不得不佩服這里設計的巧妙。筆者有理由相信能看到這里的諸位讀者一定是有實力並且有強烈求知的欲望的人,那我們在來看一下replay、restore方法的實現。
3.2.2.3 replay
1public static Object replay(@Nonnull Object captured) {
2 @SuppressWarnings("unchecked")
3 Map<TransmittableThreadLocal<?>, Object> capturedMap = (Map<TransmittableThreadLocal<?>, Object>) captured; // @1
4 Map<TransmittableThreadLocal<?>, Object> backup = new HashMap<TransmittableThreadLocal<?>, Object>();
5
6 for (Iterator<? extends Map.Entry<TransmittableThreadLocal<?>, ?>> iterator = holder.get().entrySet().iterator(); // @2
7 iterator.hasNext(); ) {
8 Map.Entry<TransmittableThreadLocal<?>, ?> next = iterator.next();
9 TransmittableThreadLocal<?> threadLocal = next.getKey();
10
11 backup.put(threadLocal, threadLocal.get()); // @3
12
13 // clear the TTL values that is not in captured
14 // avoid the extra TTL values after replay when run task
15 if (!capturedMap.containsKey(threadLocal)) { // @4
16 iterator.remove();
17 threadLocal.superRemove();
18 }
19 // set values to captured TTL
20 setTtlValuesTo(capturedMap); // @5
21
22 // call beforeExecute callback
23 doExecuteCallback(true); // @6
24
25 return backup; // @7
26}
代碼@1:首先解釋一下兩個局部變量的含義:
-
capturedMap
子線程從父線程捕獲的線程本地遍歷。 -
backup
線程池中處理本次任務的線程中原先存在的本地線程變量。
代碼@2:holder.get(),這里是子線程中原先存在的本地線程變量(即線程池中分配來執行本次任務的線程),然后遍歷它,將其存儲在backUp(@3)。
代碼@4:從這里開始,開始根據父線程的本地變量來重放當前線程,如果父線程中不包含的threadlocal對象,將從本地線程變量中移除。
代碼@5:遍歷父線程中的本地線程變量,在子線程中重新執行一次threadlocal.set方法。
代碼@6:執行beforeExecute()鈎子函數。
代碼@7:返回線程池原線程的本地線程變量,供本次調用后恢復上下文環境。
3.2.2.4 restore
恢復線程中子線程原先的本地線程變量,即恢復線程,本次執行並不會污染線程池中線程原先的上下文環境,精妙。我們來看看其代碼實現:
1public static void restore(@Nonnull Object backup) {
2 @SuppressWarnings("unchecked")
3 Map<TransmittableThreadLocal<?>, Object> backupMap = (Map<TransmittableThreadLocal<?>, Object>) backup; // @1
4 // call afterExecute callback
5 doExecuteCallback(false); // @2
6
7 for (Iterator<? extends Map.Entry<TransmittableThreadLocal<?>, ?>> iterator = holder.get().entrySet().iterator(); // @3
8 iterator.hasNext(); ) {
9 Map.Entry<TransmittableThreadLocal<?>, ?> next = iterator.next();
10 TransmittableThreadLocal<?> threadLocal = next.getKey();
11
12 // clear the TTL values that is not in bac1kup
13 // avoid the extra TTL values after restore
14 if (!backupMap.containsKey(threadLocal)) { // @4
15 iterator.remove();
16 threadLocal.superRemove();
17 }
18 }
19
20 // restore TTL values
21 setTtlValuesTo(backupMap); // @5
22}
代碼@1:獲取備份好的線程本地上下文。
代碼@2:執行afterExecute()鈎子函數。
代碼@3:遍歷本地線程變量,將不屬於backUpMap中存在的線程本地上下文移除(@4)。
代碼@5:遍歷備份的本地線程本地,在本地線程中重新執行threadlocal#set方法,實現線程本地變量的還原。
總結
本文介紹到這里了,詳細介紹了ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal的實現原理,並從ThreadLocal、InheritableThreadLocal的局限性,最終引出TransmittableThreadLocal,為全鏈路壓測中壓測標記的透傳打下堅實的基礎。
各位可愛的讀者,您能看到這里,我相信應該收獲滿滿,有勞幫忙點個贊,謝謝您的鼓勵。
本文重點是關注ThreadLocal的應用場景,對於性能方面未曾關注,其實在Netty的實現中專門為了優化ThreadLocal訪問的性能,推出了FastThreadLocal實現,如果有興趣,可以查閱筆者的另外一篇博文:ThreadLocal原理分析與性能優化思考:
https://blog.csdn.net/prestigeding/article/details/54945658
https://mp.weixin.qq.com/s/a6IGrOtn1mi0r05355L5Ng