| 操作 | 作用對象 | 說明 |
| lock | 主記憶體變量 | 標記變量為線程獨佔 |
| unlock | 主記憶體變量 | 釋放獨佔狀態 |
| read | 主記憶體變量 | 從主記憶體傳輸變量到工作記憶體 |
| load | 工作記憶體變量 | 將 read 取得的值放入工作記憶體副本 |
| use | 工作記憶體變量 | 傳遞變量值給執行引擎 |
| assign | 工作記憶體變量 | 執行引擎值賦給工作記憶體副本 |
| store | 工作記憶體變量 | 將工作記憶體值傳輸回主記憶體 |
| write | 主記憶體變量 | 將 store 取得的值寫入主記憶體變量 |
規則:read/load、store/write 必須成對出現,保證操作有序性。
3. 併發編程三大特性
- 原子性:操作不可分割,要麼全執行要麼全不執行。JMM 透過
lock/unlock 保證,Java 對應 synchronized。
- 可見性:一線程修改共享變量,其他線程立即可見。
volatile、synchronized、final 可保證。
- 有序性:單線程內有序,多線程觀察可能無序 (指令重排)。
volatile 禁止指令重排,synchronized 鎖內有序。
指令重排範例
// 線程 1
int a = readFileName();
writeFile(a);
initialized = true; // 可能被重排到 writeFile 之前!
// 線程 2
while (!initialized) { sleep(); }
doSomeThing(); // 若 initialized 先為 true,a 尚未初始化,邏輯錯誤
單線程語義不變,但多線程下順序關鍵。volatile 可修飾 initialized 禁止重排。
4. volatile 關鍵字雙重語義
- 可見性:寫入
volatile 變量立即刷新到主記憶體;讀取時強制從主記憶體獲取最新值。
- 禁止指令重排:編譯器/運行時不會重排
volatile 讀寫與其前後操作。
經典失效場景:非原子操作
public volatile int inc = 0;
public void increase() { inc++; } // 非原子:讀取-修改-寫入 三步
// 200 線程各增 50 次,預期 10000,實際 < 10000
// 原因:inc++ 非原子,volatile 僅保證可見性,不保證原子性
結論:volatile 不保證原子性,複合操作需用 synchronized、AtomicInteger 或 Lock。
volatile 有序性邊界
volatile boolean initialized = false;
public void run() {
context = readFileName(); // 1
writeFile(context); // 2 (1,2 可重排)
initialized = true; // 3 (volatile 寫)
play(); // 4
drawable(); // 5 (4,5 可重排)
}
volatile 寫 (3) 之前的操作 (1,2) 不會重排到 (3) 之後;volatile 寫 (3) 之後的操作 (4,5) 不會重排到 (3) 之前。但 (1,2) 內部、(4,5) 內部仍可重排。這是「部份有序性」。
5. 實測代碼與現象
public class VolatileTest2 {
public volatile int inc = 0;
static CountDownLatch latch = new CountDownLatch(200);
public void increase() { inc++; }
public static void main() {
VolatileTest2 test = new VolatileTest2();
for (int i = 0; i < 200; i++) {
new Thread(() -> {
for (int j = 0; j < 50; j++) test.increase();
latch.countDown();
}).start();
}
latch.await();
System.out.println(test.inc); // 輸出 9950 (非 10000)
}
}
作者註:Android 23 (API 23) 測試竟恆為 10000,原因未明 (可能 JIT 優化或測試環境差異)。
6. 總結
- JMM 定義線程與主記憶體交互規範,解決可見性、有序性、原子性
volatile 輕量級同步工具:保證可見性 + 禁止指令重排
volatile 不能 替代 synchronized 解決原子性問題
- 適用場景:狀態標記 (
boolean flag)、單次寫入多次讀取、雙重檢查鎖 (DCL) 模式
參考資源
- 上一篇文章: Java super 與 this 關鍵字深度解析:繼承鏈中的方法調用與變量訪問機制
- 下一篇文章: Spring + MyBatis 整合完整配置指南:從 XML 到註解驅動開發