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 自動化
- 理論、感悟、影片