從另一個角度理解java併發

掌握了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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章