在研讀《Thinking in Java》多線程章節時,發現幾個核心概念極易混淆,雖然這些並非新知識,卻是開發與閱讀源碼時的基石。本文將對 volatile、ThreadLocal、synchronized 三大關鍵字進行系統性總結,幫助開發者建立清晰的認知框架。
volatile 主要用於多線程環境下的變量同步。在預設情況下,為了效能,每個線程運行時會將主內存中的變量拷貝一份到自己的工作內存(緩存)中操作,這極易導致多線程間的變量副本不一致,或與主內存值不一致。
當變量被 volatile 修飾後,該變量將不會被緩存到線程的工作內存中,它會告訴編譯器不要對該變量的讀寫操作進行任何優化(指令重排),確保讀寫直接針對主內存。這保證了可見性:當一個線程修改了該變量,其他線程能立即看到最新值。同時,其效能優於 synchronized。
務必注意:volatile 僅能確保操作的是同一塊內存(可見性),無法保證操作的原子性。因此它通常用於聲明基本類型變量,使簡單的賦值與讀取操作具有原子性(不被中斷)。但當變量的值依賴於其之前的值時(如 i++),volatile 即刻失效,這是由其語義決定的。
誤區警示:切勿以為加上
volatile就萬事大吉,所有操作都變成原子操作,從而不再需要synchronized。複合操作(檢查後執行、讀取修改寫入)仍需鎖機制保護。
首先澄清:ThreadLocal 與「本地線程」毫無關係,也不是一個特殊的 Thread。它本質上是一個線程局部變量(實際是一個 Map)。它為每個使用該變量的線程提供獨立的變量副本,使得每個線程都能獨立改變自己的副本,互不干擾。
這是一種以空間換時間的策略(與 synchronized 相反):以消耗更多內存為代價,大幅降低線程同步帶來的效能開銷,並簡化併發控制的複雜度。
Android 中 Looper 的實現是 ThreadLocal 的教科書級應用,同時展示了其基本用法:
public class Looper {
private static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
public static final Looper myLooper() {
return sThreadLocal.get();
}
// ... 其他代碼省略
}
結論:ThreadLocal 無法替代 synchronized,後者的功效範圍更廣(同步機制)。
synchronized 是 Java 基於鎖機制實現的關鍵字,主要有同步方法和同步代碼塊兩種用法。Java 中每個對象天然持有一把唯一的鎖(監視器/Monitor)。當線程調用對象的 synchronized 方法時,該對象被加鎖(可重入,計數器遞增),其他試圖調用該對象中 synchronized 方法的線程將被阻塞。類級別同步則鎖定 Class 對象。
synchronized 是同步機制中最安全的方式,其他任何手段(如 volatile、ThreadLocal、CAS 等)在特定場景下都有風險或局限性。雖然它付出的代價最大(上下文切換、內核態切換等),但在正確性至上的場景下,它是首選。
| 關鍵字 | 核心作用 | 保證特性 | 適用場景 | 效能 |
|---|---|---|---|---|
| volatile | 變量可見性、禁止指令重排 | 可見性 ✅ / 原子性 ❌ | 狀態標誌、單次賦值、雙重檢查鎖 | 高(無鎖) |
| ThreadLocal | 線程數據隔離 | 隔離性 ✅ | 線程獨享上下文(如 DB 連接、Session、Looper) | 高(無鎖,佔內存) |
| synchronized | 互斥同步、內存屏障 | 原子性 ✅ / 可見性 ✅ / 有序性 ✅ | 複合操作、臨界區保護、多變量協同 | 較低(重量級鎖,JDK 1.6+ 大幅優化) |
java.util.concurrent 包)。volatile;線程獨享資源用 ThreadLocal(注意內存洩漏風險,用完 remove())。synchronized 或 ReentrantLock。synchronized 的鎖升級過程(偏向鎖 → 輕量級鎖 → 重量級鎖),避免過度優化導致錯誤。