同步機制

資料一致性原則

撰寫多執行緒應用程式時,確保資料一致性的兩大原則:

互斥性 (Mutual Exclusion)

Critical section 裡的程式碼一次只能被一個執行緒執行,即在同一時間只允許一個執行緒訪問該變量。

可見性 (Visibility)

共享資料的值被某執行緒更改時,其他執行緒可及時看見。

volatile 關鍵字

在 Java 裡,每個執行緒有各自的記憶體空間 (working memory),當執行完一段操作後,執行緒會再將剛才使用到的變數的值更新到主記憶體 (main memory) 裡,其他執行緒則可從主記憶體讀取到變數的最新值。

上述特性加快了程式處理的效率,但在多執行緒環境裡卻可能為我們帶來變數可視性 (visibility) 的問題。

當一個變數的讀取和寫入發生在不同的執行緒時,讀取變數的執行緒有時無法及時看到變數的值的改變(被其他執行緒寫入),導致資料不一致。此時,可在變數前加上 volatile,此變數會改為不使用各執行緒的 working memory,永遠從主記憶體做存取與讀寫。

synchronized 關鍵字

不同 Thread 可能同時存取同一份資源。synchronized 的目的是控制每次只能有一個 Thread 在使用同一份資源(共同一個物件),另外的 Thread 無法同時使用此同一資源。

程式發生異常時,會自動釋放 Thread 和 Lock,不會導致 Deadlock 發生。但不能讓等待的 Thread Response 中斷,等待的 Thread 會一直等待下去。

鎖物件 (Lock Object)

作為鎖的物件:

  1. synchronized method - 鎖是當前物件實例
  2. static synchronized method - 鎖是當前類別的 Class 物件
  3. synchronized(obj) - 鎖是括號中指定的物件

synchronized 的限制

  1. 同一時間只有一個執行緒可以進入 synchronized 區塊
  2. 無法保證等待中的執行緒獲得 synchronized 區塊的順序

效能考量

  • 低開銷:當 synchronized 區塊未被競爭(尚未被鎖定)
  • 高開銷:當 synchronized 區塊被競爭(已被其他執行緒鎖定)

volatile vs synchronized

特性volatilesynchronized
互斥性
可見性
適用情境單純的讀寫操作複合操作
效能較好較差
volatile 可以幫助我們寫出更簡潔的程式碼,相較用 synchronized 鎖住某個區塊,因為用 volatile 像是將同步責任交給 JVM,會比我們自己處理更不容易出錯。但如果宣告為 volatile 的變數經常被使用的話,可能導致程式的效能不如鎖住整個區塊。

CPU 快取一致性

  flowchart LR
    subgraph CPU1[CPU Core 1]
        C1[Cache]
    end
    subgraph CPU2[CPU Core 2]
        C2[Cache]
    end
    MM[Main Memory]

    C1 <--> MM
    C2 <--> MM

可見性問題

當不同 CPU Core 的 Thread 共享同一個 Memory(如 Class),若沒有正確的同步機制,一個執行緒對變數的修改可能無法及時被其他執行緒看到。

Happens-Before 保證

Happens-Before 是一組限制指令重排序的規則,用於避免指令重排序破壞 Java 的可見性保證。

  • volatile 的 happens-before 保證:對 volatile 變數的寫入 happens-before 後續對該變數的讀取
  • synchronized 的 happens-before 保證:釋放鎖 happens-before 後續獲取同一個鎖

Race Condition

當兩個或多個執行緒以可能導致不正確結果的方式存取相同變數時,就會發生競態條件。

發生條件

  • 兩個或多個執行緒同時讀寫相同的變數或資料
  • 執行緒使用以下模式存取變數:
    • Check-then-act:檢查後執行
    • Read-modify-write:修改的值依賴於先前讀取的值
  • 執行緒對變數的存取不是原子性的

解決方案

使用 synchronized 區塊確保 mutual exclusion:

CheckThenAct.java
synchronized (lock) {
    if (condition) {
        // 執行操作
    }
}

參考資源