前一篇提到正確的並行程式其實就是針對 mutable shared state 作好存取的管控(synchronized blocks)當時的重點放在如何避免多個執行緒同時存取。接下來幾篇的重點會放在一些共享/公開物件的技巧,使得多個執行緒不會有存取的問題
時常有個錯誤的認知以為同步就是透過原子操作或是針對重點部份作區隔。然而同步還有另一個更為重要的面向,也就是記憶體的可視性(memory visibility)
同步不只是要避免多個執行緒存取 shared state,同時還要確保其中一個執行緒修改 state 時,其他的執行緒都可以看見這項修改
可視性是個不易察覺的議題,因為它並不直覺。在單執行緒中,寫入一個值到變數後,如果中間沒任何其他的寫入,讀取到的值應該會等同於先前寫入的值
然而,如果是多執行緒,上面陳述的狀況就不見得會成立了,以下面的程式為例:
public class NoVisibility {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
public void run() {
while (!ready)
Thread.yield();
System.out.println(number);
}
}
public static void main(String[] args) {
new ReaderThread().start();
number = 42;
ready = true;
}
}
上述的 NoVisibility 所表達的是一種不新鮮的狀態(stale state)
以下的程式片段,各位看看有什麼問題
public class MutableInteger {
private int value;
public int get() {
return value;
}
public void set(int value) {
this.value = value;
}
}
public class SynchronizedInteger {
private int value;
public synchronized int get() {
return value;
}
public synchronized void set(int value) {
this.value = value;
}
}
這麼小範圍的 synchronized method 其實就比較不用擔心效能問題了
也許你會覺得只要針對 setter 作同步就好了啊,但其實 getter 未作同步還是有機會看到 stale state
內建鎖(Intrinsic locking) 可以讓 Thread A 對 shared mutable state 做的事在 Thread B 可見
所以小結一下,同步(synchronization),是為了:
Java 提供另一個確保 Visibility 的方法,就是 volatile
//...(略)
volatile boolean asleep;
while(!asleep)
countSheep();