自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

ThreadLocal:線程專屬的變量

開發(fā) 后端
ThreadLocal是 java 提供的一個(gè)方便對(duì)象在本線程內(nèi)不同方法中傳遞和獲取的類。用它定義的變量,僅在本線程中可見和維護(hù),不受其他線程的影響,與其他線程相互隔離。

[[390097]]

一、ThreadLocal 簡(jiǎn)介

ThreadLocal是 java 提供的一個(gè)方便對(duì)象在本線程內(nèi)不同方法中傳遞和獲取的類。用它定義的變量,僅在本線程中可見和維護(hù),不受其他線程的影響,與其他線程相互隔離。

那 ThreadLocal 到底解決了什么問題,又適用于什么樣的場(chǎng)景?

  • This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID). Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

核心意思是

ThreadLocal 提供了線程本地的實(shí)例。它與普通變量的區(qū)別在于,每個(gè)使用該變量的線程都會(huì)初始化一個(gè)完全獨(dú)立的實(shí)例副本。ThreadLocal 變量通常被private static修飾。當(dāng)一個(gè)線程結(jié)束時(shí),它所使用的所有 ThreadLocal 相對(duì)的實(shí)例副本都可被回收。

總的來說,ThreadLocal 適用于每個(gè)線程需要自己獨(dú)立的實(shí)例且該實(shí)例需要在多個(gè)方法中被使用,也即變量在線程間隔離而在方法或類間共享的場(chǎng)景。后文會(huì)通過實(shí)例詳細(xì)闡述該觀點(diǎn)。另外,該場(chǎng)景下,并非必須使用 ThreadLocal ,其它方式完全可以實(shí)現(xiàn)同樣的效果,只是 ThreadLocal 使得實(shí)現(xiàn)更簡(jiǎn)潔。

二、ThreadLocal 使用

ThreadLocal 通過 set 方法可以給變量賦值,通過 get 方法獲取變量的值。當(dāng)然,也可以在定義變量時(shí)通過 ThreadLocal.withInitial 方法給變量賦初始值,或者定義一個(gè)繼承 ThreadLocal 的類,然后重寫 initialValue 方法。

下面通過如下代碼說明 ThreadLocal 的使用方式:

  1. public class TestThreadLocal 
  2.     private static ThreadLocal<StringBuilder> builder = ThreadLocal.withInitial(StringBuilder::new); 
  3.  
  4.     public static void main(String[] args) 
  5.     { 
  6.         for (int i = 0; i < 5; i++) 
  7.         { 
  8.             new Thread(() -> { 
  9.                 String threadName = Thread.currentThread().getName(); 
  10.                 for (int j = 0; j < 3; j++) 
  11.                 { 
  12.                     append(j); 
  13.                     System.out.printf("%s append %d, now builder value is %s, ThreadLocal instance hashcode is %d, ThreadLocal instance mapping value hashcode is %d\n", threadName, j, builder.get().toString(), builder.hashCode(), builder.get().hashCode()); 
  14.                 } 
  15.  
  16.                 change(); 
  17.                 System.out.printf("%s set new stringbuilder, now builder value is %s, ThreadLocal instance hashcode is %d, ThreadLocal instance mapping value hashcode is %d\n", threadName, builder.get().toString(), builder.hashCode(), builder.get().hashCode()); 
  18.             }, "thread-" + i).start(); 
  19.         } 
  20.     } 
  21.  
  22.     private static void append(int num) { 
  23.         builder.get().append(num); 
  24.     } 
  25.  
  26.     private static void change() { 
  27.         StringBuilder newStringBuilder = new StringBuilder("HelloWorld"); 
  28.         builder.set(newStringBuilder); 
  29.     } 

在例子中,定義了一個(gè) builder 的 ThreadLocal 對(duì)象,然后啟動(dòng) 5 個(gè)線程,分別對(duì) builder 對(duì)象進(jìn)行訪問和修改操作,這兩個(gè)操作放在兩個(gè)不同的函數(shù) append、change 中進(jìn)行,兩個(gè)函數(shù)訪問 builder 對(duì)象也是直接獲取,而不是放入函數(shù)的入?yún)⒅袀鬟f進(jìn)來。

代碼輸出如下:

  1. thread-0 append 0, now builder value is 0, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 566157654 
  2. thread-0 append 1, now builder value is 01, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 566157654 
  3. thread-4 append 0, now builder value is 0, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 654647086 
  4. thread-3 append 0, now builder value is 0, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1803363945 
  5. thread-2 append 0, now builder value is 0, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1535812498 
  6. thread-1 append 0, now builder value is 0, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 2075237830 
  7. thread-2 append 1, now builder value is 01, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1535812498 
  8. thread-3 append 1, now builder value is 01, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1803363945 
  9. thread-4 append 1, now builder value is 01, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 654647086 
  10. thread-0 append 2, now builder value is 012, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 566157654 
  11. thread-0 set new stringbuilder, now builder value is HelloWorld, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1773033190 
  12. thread-4 append 2, now builder value is 012, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 654647086 
  13. thread-4 set new stringbuilder, now builder value is HelloWorld, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 700642750 
  14. thread-3 append 2, now builder value is 012, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1803363945 
  15. thread-3 set new stringbuilder, now builder value is HelloWorld, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1706743158 
  16. thread-2 append 2, now builder value is 012, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1535812498 
  17. thread-2 set new stringbuilder, now builder value is HelloWorld, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1431127699 
  18. thread-1 append 1, now builder value is 01, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 2075237830 
  19. thread-1 append 2, now builder value is 012, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 2075237830 
  20. thread-1 set new stringbuilder, now builder value is HelloWorld, ThreadLocal instance hashcode is 796465865, ThreadLocal instance mapping value hashcode is 1970695360 
  • 從輸出中 1~6 行可以看出,不同線程訪問的是同一個(gè) builder 對(duì)象(不同線程輸出的 ThreadLocal instance hashcode 值相同),但是每個(gè)線程獲得的 builder 對(duì)象存儲(chǔ)的實(shí)例 StringBuilder 不同(不同線程輸出的 ThreadLocal instance mapping value hashcode值不相同)。
  • 從輸出中1~2、9~10 行可以看出,同一個(gè)線程中修改 builder 對(duì)象存儲(chǔ)的實(shí)例的值時(shí),并不會(huì)影響到其他線程的 builder 對(duì)象存儲(chǔ)的實(shí)例(thread-4 線程改變存儲(chǔ)的 StringBuilder 的值并不會(huì)引起 thread-0 線程的 ThreadLocal instance mapping value hashcode 值發(fā)生改變)
  • 從輸出中 9~13 行可以看出,一個(gè)線程對(duì) ThreadLocal 對(duì)象存儲(chǔ)的值發(fā)生改變時(shí),并不會(huì)影響其他的線程(thread-0 線程調(diào)用 set 方法改變本線程 ThreadLocal 存儲(chǔ)的對(duì)象值,本線程的 ThreadLocal instance mapping value hashcode 發(fā)生改變,但是 thread-4 的 ThreadLocal instance mapping value hashcode 并沒有因此改變)。

三、ThreadLocal 原理

ThreadLocal 能在每個(gè)線程間進(jìn)行隔離,其主要是靠在每個(gè) Thread 對(duì)象中維護(hù)一個(gè) ThreadLocalMap 來實(shí)現(xiàn)的。因?yàn)槭蔷€程中的對(duì)象,所以對(duì)其他線程不可見,從而達(dá)到隔離的目的。那為什么是一個(gè) Map 結(jié)構(gòu)呢。主要是因?yàn)橐粋€(gè)線程中可能有多個(gè) ThreadLocal 對(duì)象,這就需要一個(gè)集合來進(jìn)行存儲(chǔ)區(qū)分,而用 Map 可以更快地查找到相關(guān)的對(duì)象。ThreadLocalMap 是 ThreadLocal 對(duì)象的一個(gè)靜態(tài)內(nèi)部類,內(nèi)部維護(hù)一個(gè) Entry 數(shù)組,實(shí)現(xiàn)類似 Map 的 get 和 put 等操作,為簡(jiǎn)單起見,可以將其看做是一個(gè) Map,其中 key 是 ThreadLocal 實(shí)例,value 是 ThreadLocal 實(shí)例對(duì)象存儲(chǔ)的值。

ThreadLocalMap

 

四、ThreadLocal 適用場(chǎng)景

如上文所述,ThreadLocal 適用于如下場(chǎng)景:

每個(gè)線程需要有自己?jiǎn)为?dú)的實(shí)例,如實(shí)現(xiàn)每個(gè)線程單例類或每個(gè)線程上下文信息(例如事務(wù)ID)。

ThreadLocal 適用于變量在線程間隔離且在方法間共享的場(chǎng)景,提供了另一種擴(kuò)展 Thread 的方法。如果要保留信息或?qū)⑿畔囊粋€(gè)方法調(diào)用傳遞到另一個(gè)方法,則可以使用 ThreadLocal 進(jìn)行傳遞。

由于不需要修改任何方法,因此可以提供極大的靈活性。

1、案例一

這里一個(gè)處理 flag 的類,通過 ThreadLocal 使用,可以保證每個(gè)請(qǐng)求都擁有唯一的一個(gè)追蹤標(biāo)記。

  1. public class TestFlagHolder { 
  2.  
  3.   private final static ThreadLocal<String> TEST_FLAG = new ThreadLocal<>(); 
  4.  
  5.   public static void set(String value) { 
  6.     TEST_FLAG.set(value); 
  7.   } 
  8.  
  9.   public static String get() { 
  10.     return TEST_FLAG.get(); 
  11.   } 
  12.  
  13.   public static String get4log() { 
  14.     if (TEST_FLAG.get() == null) { 
  15.       return "-"
  16.     } 
  17.     return TEST_FLAG.get(); 
  18.   } 
  19.  
  20.   public static void remove() { 
  21.     TEST_FLAG.remove(); 
  22.   } 
  23.  

2、案例二

在同一線程中 trace 信息的傳遞:

  1. ThreadLocal<String> traceContext = new ThreadLocal<>(); 
  2.  
  3. String traceId = Tracer.startServer(); 
  4. traceContext.set(traceId) //生成trace信息 傳入threadlocal 
  5. ... 
  6. Tracer.startClient(traceContext.get()); //從threadlocal獲取trace信息 
  7. Tracer.endClient(); 
  8. ... 
  9. Tracer.endServer(); 

3、案例三

給同一個(gè)請(qǐng)求的每一行日志增加一個(gè)相同的標(biāo)記。這樣,只要拿到這個(gè)標(biāo)記就可以查詢到這個(gè)請(qǐng)求鏈路上所有步驟的耗時(shí)了,我們把這個(gè)標(biāo)記叫做 requestId,我們可以在程序的入口處生成一個(gè) requestId,然后把它放在線程的上下文中,這樣就可以在需要時(shí)隨時(shí)從線程上下文中獲取到 requestId 了。

簡(jiǎn)單的代碼實(shí)現(xiàn)就像下面這樣:

  1. String requestId = UUID.randomUUID().toString(); 
  2. ThreadLocal<String> tl = new ThreadLocal<String>(){ 
  3.     @Override 
  4.     protected String initialValue() { 
  5.         return requestId; 
  6.     } 
  7. }; //requestId存儲(chǔ)在線程上下文中 
  8. long start = System.currentTimeMillis(); 
  9. processA(); 
  10. Logs.info("rid : " + tl.get() + ", process A cost " + (System.currentTimeMillis() - start)); // 日志中增加requestId 
  11. start = System.currentTimeMillis(); 
  12. processB(); 
  13. Logs.info("rid : " + tl.get() + ", process B cost " + (System.currentTimeMillis() - start)); 
  14. start = System.currentTimeMillis(); 
  15. processC(); 
  16. Logs.info("rid : " + tl.get() + ", process C cost " + (System.currentTimeMillis() - start)); 

有了 requestId,你就可以清晰地了解一個(gè)調(diào)用鏈路上的耗時(shí)分布情況了。

五、小結(jié)

無論是單體系統(tǒng)還是微服務(wù)化架構(gòu),無論是鏈路打標(biāo)還是服務(wù)追蹤,你都需要在系統(tǒng)中增加 TraceId,這樣可以將你的鏈路串起來,給你呈現(xiàn)一個(gè)完整的問題場(chǎng)景。如果 TraceId 可以在客戶端上生成,在請(qǐng)求業(yè)務(wù)接口的時(shí)候傳遞給服務(wù)端,那么就可以把客戶端的日志體系也整合進(jìn)來,對(duì)于問題的排查幫助更大。

同時(shí),在全鏈路壓測(cè)框架中,Trace 信息的傳遞功能是基于 ThreadLocal 的。但實(shí)際業(yè)務(wù)中可能會(huì)使用異步調(diào)用,這樣就會(huì)丟失 Trace 信息,破壞了鏈路的完整性。所以在實(shí)際的項(xiàng)目建議大家不要輕意使用 ThreadLocal。

參考資料:

[1]:《高并發(fā)系統(tǒng)設(shè)計(jì)40問》

[2]:https://juejin.cn/post/6844904016288317448#heading-6

本文轉(zhuǎn)載自微信公眾號(hào)「7DGroup」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系7DGroup公眾號(hào)。

 

責(zé)任編輯:武曉燕 來源: 7DGroup
相關(guān)推薦

2023-10-19 08:30:58

線程源碼thread

2016-08-31 15:50:50

PythonThreadLocal變量

2016-11-07 21:59:52

threadpython

2016-08-31 15:41:19

PythonThreadLoca變量

2024-04-08 10:09:37

TTLJava框架

2022-07-26 07:14:20

線程隔離Thread

2023-02-15 09:34:20

公共字段mybatis變量

2024-11-11 10:40:19

Java變量副本

2024-08-26 08:29:55

2024-08-13 08:48:50

2020-11-09 09:03:35

高并發(fā)多線程ThreadLocal

2024-10-28 08:15:32

2022-08-01 07:42:17

線程安全場(chǎng)景

2022-09-22 13:56:56

線程Java

2023-09-08 08:20:46

ThreadLoca多線程工具

2018-10-25 15:24:10

ThreadLocal內(nèi)存泄漏Java

2020-07-20 15:20:44

ThreadLocalJava多線程

2022-10-25 10:20:31

線程變量原理

2025-04-01 05:22:00

JavaThread變量

2024-03-22 13:31:00

線程策略線程池
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)