昨天看 Effective Java 提到 synchronization 同時提供 exclusive execution 和 communication, 但多數人會忽略後面這點。原本不懂這是什麼意思, 看了《Synchronization and the Java Memory Model》才知道是怎麼回事, Java Memory Model 真的和直觀的想法很不一樣。記錄一下筆記。
final class SetCheck { private int a = 0; private long b = 0; void set() { a = 1; b = -1; } boolean check() { return ((b == 0) || (b == -1 && a == 1)); } }
這段程式有可能 return false, 因為 set() 裡的 a、b 設值可能會順序相反, set 和 check 可能同時被交錯執行。Java 只有保證單一 thread 自己執行的時候, 看起來像按順序由上而下、由左而右執行 (文中用 as-if-serial 表示, 這詞還滿妙的)。
在 multi-thread 時, 情況不同, 各個 thread 有自己的 memory, Java 沒有保證什麼時候 thread 之間才會看到更新後的情況, 這點滿可怕的。但可以用 synchronization lock 或宣告 volatile 強迫 thread 之間同步資料。我想這就是書上說 communication 的意思。
Java 有保證除 long 和 double 外的欄位, 更新值的時候是 atomic 的 (也就是包含 reference), 而 volatile 宣告的欄位也都是 atomic, 包含 volatile long 和 double。這裡要注意幾點:
- i++ 包含 i + 1 和設值兩個操作, 沒有 atomic。所以設 volatile 也會有危險, 有需要的話還是要用 synchronized, 或照 Effective Java 的建議, 看能不能用 java.util.concurrent.atomic 的類別滿足需求。
- long、double 外的欄位有 atomic, 但沒保證更新後其它 thread 會讀到新值。若要其它thread 不會讀到 stale value, 要宣告 volatile, 或在讀取前加上 synchronized。
- array 有 volatile 不表示裡面的元素有 volatile (這還算直覺)。
後面說明各種特殊情況, 還有強調因為 CPU 管理 cache 方式, 有些時候其實 JVM 沒有保證 thread 之間的資料有更新, 但執行起來卻像有這一回事, 讓這種 bug 更難重製。
Visibility 那段值得細讀, 說明什麼情況下可確保 thread 之間有取到或寫入最新的值。像 start Thread 後會確保該 thread 讀到最新的值, 所以, 盡量在 start() 前建立物件, 還有別在 constructor 內 start thread, 避免 thread 執行後取得不完整的狀態。
2011-09-06 更新: 備忘簡短易懂的入門文件: Java Concurrency / Multithreading - Tutorial。
沒有留言:
張貼留言