在 Java 中 synchronized 關鍵字保證在同一時間只有一個執行緒在執行該區塊的程式。Java 保證在讀或寫一個變數是原子性 (atomic),double 及 long 類型為例外。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
沒有留言:
張貼留言