ThreadLocal 並非一個「區域執行緒」,而是執行緒區域變數。它為每個使用該變數的執行緒提供獨立的變數副本,實現執行緒層級的資料隔離。更精確的命名應為 ThreadLocalVar。從執行緒視角看,每個執行緒隱式持有對其副本的引用;執行緒結束且 ThreadLocal 實例可回收時,副本會被 GC 回收。
面對多執行緒共享資源問題:
實現原理:ThreadLocal 內部維護一個 ThreadLocalMap,Key 為 ThreadLocal 實例,Value 為執行緒專屬變數副本。
| 方法 | 說明 |
|---|---|
ThreadLocal() | 建立執行緒區域變數 |
T get() | 取得當前執行緒副本值,首次呼叫會觸發 initialValue() 初始化 |
protected T initialValue() | 回傳初始值,可覆寫自訂;若先呼叫 set() 則不會再執行 |
void set(T value) | 設定當前執行緒副本值 |
void remove() | 移除當前執行緒副本,減少記憶體佔用,下次 get() 會重新初始化 |
傳統單例 ConnectionManager 在多執行緒下存在兩大隱患:
為每個執行緒建立獨立連線副本,避免同步開銷,同時復用連線降低資料庫壓力:
public class ConnectionManager {
private static final ThreadLocal threadLocal = new ThreadLocal() {
@Override
protected Connection initialValue() {
try {
return DriverManager.getConnection(URL, USER, PASS);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
};
public static Connection getConnection() {
return threadLocal.get();
}
public static void closeConnection() {
Connection conn = threadLocal.get();
if (conn != null) {
try { conn.close(); } catch (SQLException ignore) {}
threadLocal.remove(); // 防止記憶體洩漏
}
}
}
public class Student {
private int age;
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
public class ThreadLocalDemo implements Runnable {
private static final ThreadLocal studentLocal = new ThreadLocal<>();
public static void main(String[] args) {
ThreadLocalDemo demo = new ThreadLocalDemo();
new Thread(demo, "Thread-A").start();
new Thread(demo, "Thread-B").start();
}
@Override
public void run() { accessStudent(); }
private void accessStudent() {
String name = Thread.currentThread().getName();
int age = new Random().nextInt(100);
System.out.println(name + " set age: " + age);
Student student = getStudent();
student.setAge(age);
System.out.println(name + " first read: " + student.getAge());
try { Thread.sleep(500); } catch (InterruptedException ignored) {}
System.out.println(name + " second read: " + student.getAge());
}
private Student getStudent() {
Student student = studentLocal.get();
if (student == null) {
student = new Student();
studentLocal.set(student);
}
return student;
}
}
輸出結果證明兩執行緒各自維護獨立 age 值,互不干擾。
static final ThreadLocal threadXxx 。getXxx() 方法:若 get() 為 null 則 new 實例並 set()。run() 或業務方法中透過 getXxx() 取得專屬實例操作。remove(),否則 ThreadLocalMap 持有強引用導致物件無法回收。