2021/04/16

[筆記] Effective Java #78 同步存取共享的可變數據

Effective Java 3rd 簡體中文版筆記 #78 同步存取共享的可變數據
在 Java 中 synchronized 關鍵字保證在同一時間只有一個執行緒在執行該區塊的程式。Java 保證在讀或寫一個變數是原子性 (atomic)doublelong 類型為例外。synchronized 的運用不該被侷限為互斥的行為,其目的更包含同步。
private static boolean stopRequested;

public static void main(String[] args) throws InterruptedException {
    Thread background = new Thread(() -> {
        int i = 0;
        while (!stopRequested)
            i++;
    });
    background.start();

    TimeUnit.SECONDS.sleep(1L);
    stopRequested = true;
}
上面這段程式碼,或許會期待暫停一秒後,background 就會停止,但在我的環境下,程式會無止盡的執行,原因在於沒有同步。JVM 會將程式碼作優化,將 while 轉變成 if + while,這是活性失敗 (liveness failure),修正問題的方式是同步存取 stopRequested 變數。
while (!stopRequested)
    i++;

if (!stopRequested) 
    while (true)
        i++;
新增 synchronized 的讀寫方法,或是將 stopRequested 宣告為 volatile,這樣也可以省略 synchronized 關鍵字。
private static boolean stopRequested;

private static synchronized void requestStop() {
    stopRequested = true;
}

private static synchronized boolean stopRequested() {
    return stopRequested;
}

public static void main(String[] args) throws InterruptedException {
    Thread background = new Thread(() -> {
        int i = 0;
        while (!stopRequested())
            i++;

    });
    background.start();

    TimeUnit.SECONDS.sleep(1L);
    requestStop();
}
下面這段程式的問題在於雖然使用 volatile,但是在 ++ 這部分並不具有原子性。如果執行緒 B 在執行緒 A 已新增 1 寫回變數前拿到變數,就無法保證 serialNumber 不會重複,應該對該方法使用 synchronized 關鍵字,最好還是使用 AtomicLong 類別會比較簡單。
private static volatile int serialNumber = 0;

public static int getSerialNumber() {
    return serialNumber++;
}
如果將可變數據控制在單一執行緒內使用,就可以避免上述的問題,當多執行緒共享可變數據時,不論是在讀或寫的方法中,都必須使用同步。如果只是執行緒之間的共享資料,沒有互斥,volatile 就可以是一種同步的方式。

轉載請註明原文網址 https://cookieandcoketw.blogspot.com/2021/04/effective-java-78-shared-mutable.html

沒有留言:

張貼留言