前文介紹了多線程搶佔共享資源的問題。無論同步還是重入鎖,都無法從根本解決資源緊缺,只是透過規則約束線程行為,而非真正滿足資源需求。
JDK 1.2 引入 java.lang.ThreadLocal,提供全新思路解決並發問題。名稱易誤解為「本地線程」,實際是「線程中的本地變量」。源於 Thread Local Storage(線程本地存儲)技術,不只 Java,C++、C#、.NET、Python、Ruby、Perl 等平台皆已實現。
使用 ThreadLocal 維護變量時,為每個使用該變量的線程提供獨立變量副本。從根本解決資源數量問題,使每線程持有相對獨立資源。線程工作時無需糾結同步,性能大幅提升。代價是更多空間消耗——典型的「以空間換時間」方案。
get():返回當前線程副本中的值set():設定當前線程副本中的值remove():移除當前線程的值initialValue():返回當前線程的「初始值」(需覆寫)public class ThreadDemo3 extends Thread {
private ThreadLocal<Student> stuLocal = new ThreadLocal<Student>();
public ThreadDemo3(Student stu) { stuLocal.set(stu); }
// ... run() 中呼叫 stuLocal.get() 會得到 null
}
原因:構造器在主線程執行,set() 將值放入主線程的 ThreadLocalMap。子線程啟動後調用 get(),從自身 ThreadLocalMap 取值,發現無對應 entry,執行 initialValue() 返回 null。
Thread 物件有 threadLocals 引用指向 ThreadLocalMapThreadLocalMap 非 java.util.Map 實作,用 WeakReference 實現鍵值對應,鍵為 ThreadLocal,值為 Objectset():從當前線程取 ThreadLocalMap,有則以自己為鍵存值;無則建立 Map 再存get():從當前線程取 Map,有則以自己為鍵取值;無則建立 Map 並存入 initialValue() 結果private ThreadLocal<Student> stuLocal = new ThreadLocal<Student>() {
@Override
protected Student initialValue() {
return new Student();
}
};
每個線程首次 get() 時自動獲得獨立的 Student 實例。
public class ThreadDemo3 implements Runnable {
private ThreadLocal<Student> stuLocal = new ThreadLocal<Student>() { ... };
public static void main(String[] args) {
ThreadDemo3 td3 = new ThreadDemo3();
new Thread(td3).start();
new Thread(td3).start();
new Thread(td3).start();
}
}
多線程共用同一 Runnable 實例,故共用同一 ThreadLocal 物件。但每線程有自己的 ThreadLocalMap,鍵為同一 ThreadLocal,值為各自獨立實例。本質無差異:真正封裝變量的是 ThreadLocalMap,ThreadLocal 只是中介。
initialValue() 方法set()/get() 操作的是當前活動線程的 ThreadLocalMap,務必認清當前線程程式執行僅需 5 秒;採用同步需 15 秒。ThreadLocal 讓每線程擁有獨立資源,無需互相謙讓,提高並發性並保證線程安全。
Struts 2.x、Spring 等流行框架也採用 ThreadLocal 解決並發問題。
ThreadLocal 非萬能藥。若實際情況不允許為每線程分配本地資源副本,同步仍有重要意義。它是將多線程數據隔離、避免同步性能問題的「以空間換時間」方案。