當前位置: 妍妍網 > 碼農

關於ThreadLocal,我有話要說...

2024-05-17碼農

ThreadLocal是Java中一個非常重要的執行緒技術。它可以讓每個執行緒都擁有自己的變量副本,避免了執行緒間的競爭和數據泄露問題。在本文中,我們將詳細介紹ThreadLocal的定義、用法及其優點。

ThreadLocal是Java中一個用來實作執行緒封閉技術的類。它提供了一個本地執行緒變量,可以在多執行緒環境下使每個執行緒都擁有自己的變量副本。每個執行緒都可以獨立地改變自己的副本,而不會影響到其他執行緒的副本。ThreadLocal的實作是基於ThreadLocalMap的,每個ThreadLocal物件都對應一個ThreadLocalMap,其中儲存了執行緒本地變量的值。

優缺點

ThreadLocal的主要優點是可以提高並行程式的效能和安全性,同時也存在一些缺點和使用場景需要註意。

優點:

  1. 提高並行效能:使用ThreadLocal可以避免多個執行緒之間的競爭,從而提高程式的並行效能。

  2. 保證執行緒安全:每個執行緒有自己獨立的變量副本,避免了執行緒安全問題。

  3. 簡化程式碼:使用ThreadLocal可以避免傳遞參數的繁瑣,簡化程式碼。

缺點:

  1. 記憶體泄漏:ThreadLocal變量副本的生命周期與執行緒的生命周期一樣長,如果執行緒長時間存在,而ThreadLocal變量沒有及時清理,就會造成記憶體泄漏。

  2. 增加資源開銷:每個執行緒都要建立一個獨立的變量副本,如果執行緒數很多,就會增加資源開銷。

  3. 不適用於共享變量:ThreadLocal適用於每個執行緒有獨立的變量副本的場景,不適用於共享變量的場景。

適用場景

  1. 執行緒安全的物件:ThreadLocal適用於需要在多個執行緒中使用的執行緒安全物件,例如SimpleDateFormat、Random等。

  2. 跨層傳遞參數:ThreadLocal可以避免在方法之間傳遞參數的繁瑣,尤其在跨層傳遞參數的場景中,可以大大簡化程式碼。

  3. 執行緒局部變量:ThreadLocal可以用於在當前執行緒中儲存和存取局部變量,例如日誌、請求資訊等。

實作原理

首先透過一張圖看下ThreadLocal與執行緒的關系圖:

  1. 每個Thread物件都有一個ThreadLocalMap型別的成員變量threadLocals,這個變量是一個鍵值對集合,用於儲存每個ThreadLocal物件對應的值。

  2. 每個ThreadLocal物件都有一個唯一的ID,用於在ThreadLocalMap中作為鍵來儲存值。

  3. 當一個執行緒第一次呼叫ThreadLocal物件的get()方法時,它會先獲取當前執行緒的ThreadLocalMap物件,然後以ThreadLocal物件的ID作為鍵,從ThreadLocalMap中獲取對應的值。

  4. 如果ThreadLocalMap中不存在對應的鍵值對,則呼叫ThreadLocal物件的initialValue()方法來初始化一個值,並將其儲存到ThreadLocalMap中。

  5. 如果ThreadLocalMap物件的參照不再需要,那麽需要手動將其置為null,這樣可以避免記憶體泄漏。

記憶體泄漏

ThreadLocal變量副本的生命周期與執行緒的生命周期一樣長,如果執行緒長時間存在,而ThreadLocal變量沒有及時清理,就會造成記憶體泄漏。為了避免記憶體泄漏,可以在使用ThreadLocal的地方及時清理ThreadLocal變量,例如線上程池中使用ThreadLocal時,需要線上程結束時手動清理ThreadLocal變量。

記憶體泄漏出現的原因:

ThreadLocalMap中的Entry物件持有ThreadLocal物件的弱參照,但是ThreadLocalMap中的Entry物件是由ThreadLocal物件強參照的。
如果ThreadLocal物件沒有及時清理,在ThreadLocal物件被垃圾回收時,ThreadLocalMap中的Entry物件仍然存在,從而導致記憶體泄漏。

解決記憶體泄漏的方法:

在使用ThreadLocal的程式碼中及時清理ThreadLocal變量。通常情況下,我們可以使用ThreadLocal的remove()方法手動清理ThreadLocal
變量,或者在使用完ThreadLocal變量後將其設定為null。

透過上圖我們可以看到,線上程方法執行過程中,ThreadLocal、ThreadLocalMap以及Thread之間的參照關系; Thread中存在一個內容threadLocals指向了ThreadLocalMap,ThreadLocal實作執行緒級別的數據隔離主要是基於該物件;在ThreadLocal中是沒有儲存任何數據,其更像一個執行緒與ThreadLocalMap間的協調器,數據儲存在ThreadLocalMap中,但是該Map的Key卻是ThreadLocal的弱參照;

一般情況下,執行緒執行完成後,待執行緒銷毀,那麽執行緒對應的內容threadLocals也會被銷毀;但是真實環境中對執行緒的使用大部份都是執行緒池,這樣在整個系統生命周期中, 執行緒都是有效的,直至執行緒池關閉。而將ThreadLocalMap的Key設定成弱參照時,經過GC後該Map的Key則變成了null,但是其Value卻一直存在,因此需要手動將key為null 的數據進行清理。

下面是一個範例演示如何避免ThreadLocal記憶體泄漏:

public classMyThreadLocal{
privatestatic ThreadLocal<String> threadLocal = new ThreadLocal<>();
publicstaticvoidset(String value){
threadLocal.set(value);
}
publicstatic String get(){
return threadLocal.get();
}
publicstaticvoidremove(){
threadLocal.remove();
}
}
public classMyRunnableimplementsRunnable{
@Override
publicvoidrun(){
MyThreadLocal.set("hello");
System.out.println(MyThreadLocal.get());
// 在使用完ThreadLocal變量後,呼叫remove()方法清理ThreadLocal變量
MyThreadLocal.remove();
}
}




在上面的程式碼中,MyThreadLocal類封裝了ThreadLocal變量的操作,MyRunnable類實作了Runnable介面,使用MyThreadLocal類來儲存和存取 ThreadLocal變量。在MyRunnable的run()方法中,使用完ThreadLocal變量後,呼叫remove()方法清理ThreadLocal變量,避免了記憶體泄漏的問題。

ThreadLocal一般會設定成static

主要是為了避免重復建立TSO(thread specific object,即與執行緒相關的變量。)我們知道,一個ThreadLocal例項對應當前執行緒中的一個TSO例項。如果把ThreadLocal聲明為某個類的例項變量(而不是靜態變量),那麽每建立一個該類的例項就會導致一個新的TSO例項被建立。而這些被建立的TSO例項是同一個類的例項。同一個執行緒可能會存取到同一個TSO(指類)的不同例項,這即便不會導致錯誤,也會導致浪費!

簡單的說就是在ThreadLocalMap中,同一個執行緒是否有必要設定多個ThreadLocal來儲存執行緒變量?

範例

下面是一個簡單的例子,演示了如何使用ThreadLocal來實作執行緒數據隔離:

public classThreadLocalTest{
privatestatic ThreadLocal<String> threadLocal = new ThreadLocal<>();
publicstaticvoidmain(String[] args)throws InterruptedException {
new Thread(() -> {
threadLocal.set("Thread A");
System.out.println("Thread A: " + threadLocal.get());
}).start();
new Thread(() -> {
threadLocal.set("Thread B");
System.out.println("Thread B: " + threadLocal.get());
}).start();
Thread.sleep(1000);
System.out.println("Main: " + threadLocal.get());
}
}



執行結果如下:

Thread A: Thread A
Thread B: Thread B
Main: null

從輸出結果可以看出,每個執行緒都擁有自己的變量副本,互不影響。而在主執行緒中,由於沒有設定過變量副本,所以返回null。

結束語

ThreadLocal是幫助我們在多個執行緒間實作執行緒對數據獨享,並不是用來解決執行緒間的數據共享問題。

>>

END

精品資料,超贊福利,免費領

微信掃碼/長按辨識 添加【技術交流群

群內每天分享精品學習資料

最近開發整理了一個用於速刷面試題的小程式;其中收錄了上千道常見面試題及答案(包含基礎並行JVMMySQLRedisSpringSpringMVCSpringBootSpringCloud訊息佇列等多個型別),歡迎您的使用。

👇👇

👇點選"閱讀原文",獲取更多資料(持續更新中