掌握了Sequential Consistency一致性模型之後,我們重新審視一下java的併發。
我們已經說過,Sequential Consistency只保留進程本地順序。上文中我們瞭解到,由於CPU指令重排序、內存多級緩存不一致等問題,硬件層次沒有提供Sequential Consistency,需要軟件開發人員實現。下面我們就來看一下,在java中如何實現Sequential Consistency。
Java中Sequential Consistency的基礎,是JVM的happens-before
關係。在Java Language Specification的內存模型中,規定了happens-before
關係,正確處理happens-before
關係,是java語言正確實現併發的基礎。顯而易見的是,在同一個線程的代碼中,前面的action happens-before
後面的action。然而它後面又說,兩個action就算有happens-before
關係,在實現中也不一定按照這個順序發生。重新排序後的順序只要能產生合法的結果,就可以接受。
It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.
注意,這裏說的是“合法”的結果,並不代表這個“合法”的結果符合你的預期。
更確切的說,兩個action如果有happens-before
關係,對於和它們沒有happens-before
關係的代碼來說,它們不一定按照這個順序發生。
舉個例子,對於同時訪問數據的兩個線程來說,一個線程裏的寫操作在另一個線程裏的讀操作看來,有可能是亂序的。
More specifically, if two actions share a happens-before relationship, they do not necessarily have to appear to have happened in that order to any code with which they do not share a happens-before relationship. Writes in one thread that are in a data race with reads in another thread may, for example, appear to occur out of order to those reads.
舉個例子來看一下吧。假設兩個線程X和Y能共同訪問兩個變量A和B,A和B初始值爲0。
在X線程中執行
A=5
B=5
在Y線程中同時讀取A和B(實際上java中沒法同時原子性的讀取兩個變量,我們可以先讀取B,再讀取A),那麼有沒有可能讀取到B=5,A=0呢?直覺上來看,是不可能的。因爲X先更新A,後更新B,如果B都讀取到了5,那A應該也是5纔對。
但是java內存模型明確指出,這種情況是有可能的。因爲某個編譯器認爲
A=5
B=5
和
B=5
A=5
也沒有什麼區別嘛,所以先執行哪個也沒關係,所以大刀闊斧的調換了順序。
所以爲了得到需要的結果,在編程時需要建立正確的happens-before
關係。建立的方法,可以參考java語言規範。
比如java語言規範就規定了對volatile
字段的寫入,happens-before
後續對該字段的讀取。happens-before
關係確定以後,不僅讓volatile
字段的值讓所有線程立即可見,還限制了對該字段訪問操作的reorder。
具體可以參考如下對volatile關鍵字的解釋。
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile