在現代多核處理器架構下,並發編程的核心挑戰在於多線程對共享數據的可見性與原子性。Java 自 1.0 版本以來便提供了 volatile 關鍵字,作為一種輕量級的同步機制,它能保証線程對變量讀寫操作的內存可見性,卻不引入鎖機制,因此避免了死鎖風險。本文將從硬件緩存模型切入,系統闡述 volatile 的語義規則、適用場景及其與 synchronized、不可變對象的協作關係。
早期單核時代,線程通過時間片輪轉共享 CPU,並發本質上是「分時複用」。然而隨著多核處理器普及,多線程可真正並行執行於不同物理核心。為了效率,每個核心擁有獨立的 L1/L2 緩存,導致同一變量在不同線程中可能存在多份副本(如圖 4.4 所示)。當線程 A 修改共享變量時,若未及時刷新至主內存,線程 B 讀取到的將是舊值,這就是「緩存可見性」問題。

synchronized 塊或方法完成時,會將對鎖對象的所有修改刷新回主內存;進入同步塊前,則會從主內存重新讀取該對象的最新狀態(圖 4.5)。換句話說,synchronized 同步的實際是「不同線程中鎖對象的內存表現形式」。

volatile 針對單個字段(包括基本類型與對象引用)提供兩條核心語義:
這相當於在每次讀寫操作周圍加了一個微型的 synchronized 塊,但不引入鎖,因此不會發生死鎖。不過,頻繁的內存屏障會帶來性能開銷,需權衡使用。
關鍵限制:寫入操作不得依賴變量的當前值。例如 volatile int count; count++; 不是線程安全的,因為自增包含「讀取-修改-寫入」三步,中間可能被其他線程打斷。此類「複合操作」必須配合鎖或原子類(AtomicInteger)完成。volatile 適合獨立狀態標記,如關閉標誌 volatile boolean shutdown;。
不可變對象(所有字段均為 final 並在構造函數中初始化)天然線程安全,因為其狀態永不改變,無「不一致狀態」之憂。構造函數參數過多時,可採用建造者模式優雅解決:
public class Update {
private final Author author;
private final String updateText;
private Update(Builder b) {
author = b.author;
updateText = b.updateText;
}
public static class Builder implements ObjBuilder<Update> {
private Author author;
private String updateText;
public Builder author(Author a) { author = a; return this; }
public Builder updateText(String t) { updateText = t; return this; }
public Update build() { return new Update(this); }
}
}
// 使用示例
Update u = new Update.Builder()
.author(myAuthor)
.updateText("Hello")
.build();
注意:final 僅保證引用不可變,若引用指向的對象內部包含可變字段(圖 4.6),仍需謹慎處理。
volatile 解決單變量可見性,適用於「寫入不依賴舊值」的獨立狀態;synchronized 或 java.util.concurrent.atomic 保證原子性;讀書筆記:《The Well-Grounded Java Developer》