天天看點

JAVA并發-DCL與JMM

首先必須聲明,[color=red]在volatile出現之前,錯誤的DCL代碼如下。在volatile出現之後,正确的DCL代碼如下[/color]。代碼如下:

[color=red]我們來剖析錯誤的代碼:[/color]

在錯誤的DCL代碼中,代碼行mark在JVM中有2種可能的執行順序(JVM會對一些沒有依賴要求的指令重排序。[color=blue]關于重排序,在下文中進行說明[/color]),分别為:

//第一種執行順序
mem=allocate()
mem.initial()
instance=mem

//第二種執行順序
mem=allocate()
instance=mem
mem.initial()
           

假設現在有2個線程a和b,線程a執行到mark行語句并按照第二種執行順序執行完instance=mem指令。此時線程b執行getInstane方法會直接傳回一個非空的instance,但是這個instance可能是未被完整建立的。這個時候,DCL就出問題了。這就是大家對DCL口誅筆伐的原因。由此可見,[color=red]錯誤的DCL的真正問題就在于:在沒有同步的情況下讀取一個共享變量,可能讀到不完整的執行個體。[/color]也就是說,不在同步代碼塊中的if(instance==null)代碼可能讀取到不完整的instance執行個體。

為了解決類似DCL中出現的問題,[color=red]JAVA在JMM中(JAVA記憶體模型)定義了一系列Happens-Before關系。如果兩個操作之間缺少Happens-Before關系,那麼JVM就可以對它們任意的重排序。[/color]比如volatile變量規則規定:對volatile變量的寫入操作必須在對該變量的讀操作之前進行。這就是DCL在使用volatile之後變得正确的原因。

[color=blue]重排序:[/color]

下面的代碼可能的輸出結果有:<1,1><1,0><0,1><0,0>這四種,首先,兩個線程的先後執行順序不同,可能one先(結果是<0,1>),可能two先(結果是<1,0>),可能one和two交替執行(結果是<1,1>);其次,由于每個線程内部的各個操作之間不存在依賴性,是以這些操作可以重排序(結果可能為<0,0>)。可見,[color=red]記憶體級别的重排序會令程式變得不可預測,是以需要正确的使用同步,以使程式滿足JMM要求的可見性規則。[/color]

繼續閱讀