16

JMM中的因果關係似乎是最令人困惑的部分。我有幾個關於JMM因果關係的問題,並允許併發程序中的行爲。爲什麼在Java內存模型中允許這種行爲?

據我所知,目前的JMM總是禁止因果關係循環。 (對嗎?)現在

,按照該JSR-133文檔,24頁,圖16中,我們有一個例子:

最初x = y = 0

主題1:

r3 = x; 
if (r3 == 0) 
    x = 42; 
r1 = x; 
y = r1; 

線程2:

r2 = y; 
x = r2; 

直觀上,r1 = r2 = r3 = 42似乎不可能。但是,它不僅被提及,而且在JMM中也是「允許的」。

對於可能性,從中我無法理解該文檔的解釋是:

編譯器可確定曾經分配給x值只有 0和42從這一點,編譯器可能推測,在我們執行r1 = x的點 處,我們剛剛執行了42到 x的寫入,或者我們剛剛讀取了x並看到了值42.在任何一種情況下,它都會合法讀取x查看價值 42.然後它可以將r1 = x更改爲r1 = 42;這將允許y = r1轉換爲y = 42並且執行得更早,導致 有問題的行爲。在這種情況下,首先寫入y承諾 。

我的問題是,究竟是什麼樣的編譯器優化? (我是編譯器無知的。)由於42僅僅是有條件地寫入的,所以當滿足if語句時,編譯器如何決定去編寫x

其次,即使編譯器這個投機的優化,並承諾y = 42和 然後最後使得r3 = 42,是不是違反了因果循環的,因爲沒有原因,現在的效果區別剩下什麼?

實際上,在同一文件(第15頁,圖7)中有一個例子,其中提到了一個類似的因果迴路,因爲這是不可接受的。

那麼這個執行命令在JMM中怎麼合法呢?

回答

6

如所解釋的,曾經寫入的x唯一的值是0和42線程1:

r3 = x; // here we read either 0 or 42 
if (r3 == 0) 
    x = 42; 
// at this point x is definitely 42 
r1 = x; 

因此JIT編譯器可以重寫r1 = xr1 = 42,並且進一步y = 42。重點是,線程1將始終無條件地寫42到y。變量r3實際上是多餘的,可以從機器代碼中完全消除。因此,該示例中的代碼僅給出了從xy的因果箭頭的外觀,但詳細分析表明實際上沒有因果關係。令人驚訝的後果是,寫入y可以提前做出承諾。

關於優化的一般注意事項:我認爲您熟悉從主內存讀取時涉及的性能損失。這就是爲什麼JIT編譯器傾向於儘可能拒絕這樣做,在這個例子中,事實證明它實際上並不需要閱讀x以便知道要寫入y的內容。

上符號的一般註釋:r1r2r3局部變量(它們可以是對堆或在CPU寄存器); x,y共享變量(這些都在主存中)。沒有考慮到這些,這些例子就沒有意義了。

1

javac沒有對代碼進行很大程度的優化。 JIT優化了代碼,但對重新排序代碼相當保守。 CPU可以重新排序執行,並且它可以很小程度地完成這個任務。

強制CPU不做指令級優化是相當昂貴的,例如,它可以減慢10倍或更多。 AFAIK,Java設計者希望指定所需的最低限度的保證,這些保證可以在大多數CPU上高效工作。

3

編譯器可以執行一些分析和優化,並用下面的代碼線程1結束:

y=42; // step 1 
r3=x; // step 2 
x=42; // step 3 

對於單線程執行,該代碼相當於原代碼等是合法的。然後,如果Thread2的代碼在步驟1和步驟2之間執行(這很可能),那麼r3也被分配42。

此代碼示例的整體思想是爲了演示正確同步的需要。

+0

@Alexei這解釋了一些。但是,編譯器不應該使用'r3 = 0'而不是'r3 = 42'嗎?或者他們只是在展示'一種可能性'! – gaganbm

+2

編譯器不會生成'r3 = 42',它只會使'r3 = x'完好無損。編譯器優化並不總是以最大深度執行。如果優化可能違反正確性的可能性很小,則會被放棄。在給定的代碼中,不存在這樣的情況,但是如果存在其他代碼,則它們可以出現。另外,編譯器可以決定'r3 = 0'與'r3 = x'的價格相同。 –