【連載 13】ThreadLocal 類

FunTester發表於2025-01-21

2.8 ThreadLocal

在使用 Java 進行效能測試的過程中,將執行緒共享的變數透過用例設計最佳化轉換成執行緒獨享,是一種非常高效的解決執行緒安全問題的思路。java.lang.ThreadLocal 可以不必提前確定執行緒的數量,不必提前分配每個執行緒所需要的物件,直接全域性定義一個 java.lang.ThreadLocal 物件,在多執行緒程式設計中使用 java.lang.ThreadLocal 提供的操作 API 即可完成執行緒獨享物件的建立、修改和其他管理操作。

2.8.1 基礎方法

ThreadLocal 實現原理基於每個執行緒都維護一個 ThreadLocalMap,這個對映表的鍵是 ThreadLocal 例項,值是對應的執行緒區域性變數。這樣,每個執行緒都可以擁有自己的獨立變數副本,不受其他執行緒影響。

首先我們看一下如何建立一個 java.lang.ThreadLocal 物件:

ThreadLocal<String> threadLocal = new ThreadLocal<>();

建立方法需要宣告泛型,下面是 ThreadLocal 的無參構造方法:

public ThreadLocal() {
}

該方法獲取到的獨享物件預設值是 null,如果你想設定其他預設值,可以使用以下語法:

ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
    @Override
    protected String initialValue() {
        return "Hello FunTester !";
    }
};

亦或者使用 java.lang.ThreadLocal#withInitial 方法建立 ThreadLocal 物件:

ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "Hello FunTester !");

initialValue 方法內容如下:

public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
    return new SuppliedThreadLocal<>(supplier);
}

使用該方法會建立一個 ThreadLocal 子類 java.lang.ThreadLocal.SuppliedThreadLocal,該類重寫了 java.lang.ThreadLocal.SuppliedThreadLocal#initialValue 方法,內容如下:

@Override
protected T initialValue() {
    return supplier.get();
}

其次我們對於變數兩個最基本的操作:獲取和修改。

  • 獲取:java.lang.ThreadLocal#get
  • 修改:java.lang.ThreadLocal#set

API 非常簡單,這裡需要注意的就是返回值和引數值的型別需要與 ThreadLocal 建立是定義的泛型型別保持一致。

2.8.2 最佳實戰

在效能測試中,ThreadLocal 通常用來將共享物件轉換成獨享解決執行緒安全的問題。下面用一個案例演示 ThreadLocal 最佳實戰:

package org.funtester.performance.books.chapter02.section8;

/**
 * ThreadLocal 演示
 */
public class ThreadLocalDemo {

    public static void main(String[] args) {
        ThreadLocal<String> threadLocal = new ThreadLocal< String >() {// 建立一個執行緒本地變數
            @Override
            protected String initialValue() { // 重寫初始化方法
                return "Hello FunTester !";
            }
        };
        for (int i = 0; i < 3; i++) {
            new Thread(() -> { // 建立並啟動3個執行緒
                System.out.println("初始值: " + threadLocal.get()); // 獲取本地變數值,並輸出
                threadLocal.set(Thread.currentThread().getName() + "    Hello FunTester !"); // 設定本地變數值
                System.out.println("修改後值: " + threadLocal.get()); // 獲取本地變數值,並輸出
            }).start();
        }
    }

}

控制檯輸出

初始值: Hello FunTester !
修改後值: Thread-0    Hello FunTester !
初始值: Hello FunTester !
修改後值: Thread-1    Hello FunTester !
初始值: Hello FunTester !
修改後值: Thread-2    Hello FunTester !

可以看到,每個執行緒列印的初始值都是一樣的,重新賦值之後,每個執行緒列印的值都變得不一樣了。這就說明每個執行緒實際獲取的物件並不是同一個物件,也就實現了將共享物件轉換成獨享物件的設計思路,解決了執行緒安全的問題。

2.8.3 使用場景

除了以上使用場景外,java.lang.ThreadLocal 在效能測試使用的場景並不多。但在 ThreadLocal 使用過程中,需要注意潛在的記憶體洩漏問題。

如果 ThreadLocal 例項在某個類中定義為 static,而該類又被類載入器載入,那麼這個 ThreadLocal 物件將一直存在於記憶體中,直到執行緒結束或者手動呼叫 remove() 方法將其移除。

所以在效能測試中如果使用 ThreadLocal 來解決執行緒安全問題,需要對執行緒管理更加嚴格。對於新手而言,使用最佳實戰中的方式是安全可靠的,若還是無法滿足需求,則應該拋棄 ThreadLocal,尋求其他簡單、可靠的解決方案。

書的名字:從 Java 開始做效能測試

如果本書內容對你有所幫助,希望各位不吝讚賞,讓我可以貼補家用。讚賞兩位數可以提前閱讀未公開章節。我也會嘗試製作本書的影片教程,包括必要的答疑。

FunTester 原創精華

【連載】從 Java 開始效能測試

  • 混沌工程、故障測試、Web 前端
  • 服務端功能測試
  • 效能測試專題
  • Java、Groovy、Go
  • 白盒、工具、爬蟲、UI 自動化
  • 理論、感悟、影片
如果覺得我的文章對您有用,請隨意打賞。您的支援將鼓勵我繼續創作!
打賞支援
暫無回覆。

相關文章