計算機內存模型與緩存一致性基礎
CPU 高速緩存導致多核心下的緩存一致性問題:線程 A 修改變量未刷新主存,線程 B 讀取舊值。解決方案:
- 總線鎖 (LOCK#):阻塞其他 CPU 訪問內存,性能極差
- MESI 緩存一致性協議:寫入時廣播失效通知,其他核心讀取時重新從主存加載
Java 內存模型 (JMM) 三大特性
| 特性 | 定義 | Java 保障機制 |
| 原子性 | 操作不可分割 | 基本類型讀寫、Atomic*、synchronized、Lock |
| 可見性 | 修改對其他線程立即可見 | volatile、synchronized、Lock、final |
| 有序性 | 執行順序符合代碼順序 | volatile、synchronized、Lock、happens-before |
volatile 雙重語義實現原理
// 源碼級理解
volatile int x = 0;
// 寫入時:StoreLoad Barrier
x = 10; // 1. 寫工作內存 2. 刷新主存 3. 發送失效通知
// 讀取時:LoadLoad + LoadStore Barrier
int y = x; // 1. 檢查緩存行有效性 2. 無效則從主存讀取 3. 禁止指令重排序
底層指令:lock 前綴 = 內存屏障
- 禁止指令重排序 (編譯器 + CPU)
- 強制刷新寫緩存到主存
- 寫操作導致其他 CPU 緩存行失效
volatile 能力邊界:不保證原子性
// 經典失敗案例
volatile int count = 0;
void increment() { count++; } // 非原子:讀-改-寫三步操作
// 10線程各加1000次,結果 < 10000
// 解決方案:
// 1. synchronized
// 2. Lock
// 3. AtomicInteger (CAS 無鎖)
volatile 適用場景:狀態標誌與雙重檢查鎖
1. 線程停止標誌
volatile boolean running = true;
void shutdown() { running = false; }
void run() {
while (running) { // 保證讀到最新值
doWork();
}
}
2. 惰性初始化雙重檢查鎖 (DCL)
public class Singleton {
// 關鍵:volatile 防止指令重排序
// new Singleton() 三步:分配內存 → 初始化對象 → 賦值引用
// 無 volatile 時可能發生:分配 → 賦值 → 初始化 (其他線程看到非 null 但未初始化完成)
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) { // 第一次檢查 (無鎖,性能)
synchronized (Singleton.class) {
if (instance == null) { // 第二次檢查 (線程安全)
instance = new Singleton();
}
}
}
return instance;
}
}
3. 獨立狀態變量 (無依賴當前值的寫入)
// 程序順序規則:單線程內代碼順序
// 鎖規則:unlock → 後續 lock
// volatile 規則:寫 volatile → 後續讀同 volatile
// 傳遞性:A hb B, B hb C → A hb C
// 線程啟動:start() → 線程內動作
// 線程終止:線程動作 → join()/isAlive()
// 實例:volatile 實現有序性
volatile boolean inited = false;
Context context = null;
void init() {
context = loadContext(); // 1
inited = true; // 2 (volatile 寫)
}
void use() {
while (!inited) sleep(); // 3 (volatile 讀)
doSomething(context); // 4 保證見到 1 的結果
}
// 2 hb 3 → 1 hb 4 (傳遞性)
volatile vs synchronized 選擇指南