發生以前並不意味着兩個任意操作的順序。更確切地說,發生的最重要的事情 - 之前所做的是綁定寫入和讀取在發生 - 在一致性之前發生。值得注意的是,它告訴讀者可以觀察哪些寫操作:最後一次寫操作發生在訂單之前,或者其他任何未訂購的寫操作發生之前(競爭)。請注意,連續兩次讀取可能會看到從不同(寫入)寫入獲得的不同值,而不會違反該要求。
E.g. JLS 17.4.5說:
應當注意的是,之前發生關係 兩者行動的存在並不一定意味着他們必須採取 發生在執行的順序。如果重新排序產生與合法執行一致的結果 ,則它不是非法的。
數據競賽令人毛骨悚然:racy讀取可以在每次讀取時返回令人驚訝的數據,而Java存儲器模型可捕獲該數據。因此,更準確的答案是產生(1,0)的執行不違反Java內存模型約束(同步順序一致性,同步順序 - 程序順序一致性,在一致性和因果關係要求之前發生),因此允許。實施方式:在硬件上,兩個負載可以在不同時間啓動和/或到達存儲器子系統,而不管它們的「程序順序」如何,因爲它們是獨立的;在編譯器中,指令調度也可能忽略獨立讀取的程序順序,從而以「逆直覺」順序將負載暴露給硬件。
如果你想看到要在程序順序中觀察,你需要一個更強的屬性。 JMM將該屬性設置爲同步操作(在您的示例中,使變量volatile
可以做到這一點),該操作將總計同步順序中的操作綁定爲一致的與程序順序。在這種情況下,(1,0)將被禁止。
插圖上的very special jcstress testcase(見注意事項的完整源):
private final Holder h1 = new Holder();
private final Holder h2 = h1;
private static class Holder {
int a;
int trap;
}
@Actor
public void actor1() {
h1.a = 1;
}
@Actor
public void actor2(IntResult2 r) {
Holder h1 = this.h1;
Holder h2 = this.h2;
h1.trap = 0;
h2.trap = 0;
r.r1 = h1.a;
r.r2 = h2.a;
}
即使在x86不重新排序負荷,產率(1,0)時,糟糕:
[OK] o.o.j.t.volatiles.ReadAfterReadTest
(fork: #1, iteration #1, JVM args: [-server])
Observed state Occurrences Expectation Interpretation
[0, 0] 16,736,450 ACCEPTABLE Doing both reads early.
[1, 1] 108,816,262 ACCEPTABLE Doing both reads late.
[0, 1] 3,941 ACCEPTABLE Doing first read early, not surprising.
[1, 0] 84,477 ACCEPTABLE_INTERESTING First read seen racy value early, and the s...
製作Holder.a
揮發性會使(1,0)消失。
是的,這是正確的。但是,編譯器不是「重新排序」線程。編譯器只是編譯。 – Elyasin
我懷疑'1,0'是可能的。這看起來非常非常錯誤。 – luk2302
我會很驚訝地看到1,0。你真的看過這個輸出嗎? – bhspencer