Java Memory Model引發的血案

來源:互聯網
上載者:User

好吧,我標題黨了。

 

之前的blog

簡單的提及過java Memory Model,不過這次卻真的碰到了由於JMM的特性導致的錯誤。

背景是跑系統的壓力測試的時候突然拋出了一個NullPointerException。這事挺奇怪的,因為已經跑了很長時間的壓力測試,可這會才莫名的報了個錯,於是找到對應的程式碼,如下所示:

 

Thread A

this.data = new Data();<br />synchronized (this) {</p><p>this.isDone = true;<br />this.notify();}<br />

 

Thread B

synchronized (this) {<br />if (this.isDone) {<br />} else {<br />this.wait();<br />}<br />}<br />this.data.do(); // throw NullPointerException<br />

 

上面兩部分代碼分別有不同的thread執行。正常執行的話,這個NullPointerException是不可能拋出的,因為在Thread A的代碼塊中,調用this.notify()之前this.data已經被賦值了。唯一的解釋是,Thread A的代碼塊的執行順序被打亂了。根據JMM的規範,是不一定保證程式碼的執行順序的。在這裡,“this.data = new Data();”這條語句可能被放在synchronized() 之後執行,於是當Thread B從this.wait()處喚醒時,this.data還沒有被賦值呢,自然就拋錯了。

 

正確的代碼也很簡單,將“this.data = new Data();”這行放入synchronized() {....}裡面:

 

Thread A

synchronized (this) {<br />this.data = new Data();<br />this.isDone = true;<br />this.notify();}<br />

 

由於採用了synchronized關鍵字,可以保證synchronized中的代碼的執行結果在其它線程進入synchronzied中之前能夠被看見。在這裡,就保證了當thread進入到 synchronized代碼中時,Thread A中的“this.data = new Data();”已經執行完了。

 

-- END --

 

-------------------------- 華麗的分割線 --------------------------------


補充一下:(因為這篇被推薦了,為了不誤人子弟...)


評論中,roamm

提到synchronzied可能會提供memory barrier的效果,目前為止這一點仍然沒有一個定論。我把這個問題今天發在了SMTH的java版,不過似乎討論也沒有最終的結果。


這個問題可以簡單的歸納為:

如果對於一段代碼

A

B

C

存在亂序執行的可能: (比如,實際的執行順序為 C -> B -> A)


那麼,改造一下這段代碼

A

synchronzied{B}

C

是否仍然存在亂序執行的可能 (比如 C -> synchronzied{B} -> A)


目前沒有結論。(應該這麼說,沒有找到任何證據可以證明synchronized提供了類似於memory barrier的效果)

 

----------------------------------  華麗麗的分割線  --------------------------------------------------


這個問題基本上有答案了,重新梳理一下吧。

還是舉例說明:

public class Test {<br />int x = 0, y = 0;<br />int a = 0, b = 0;<br />public void task1() {<br />a = 1;<br />x = b;<br />}<br />public void task2() {<br />b = 1;<br />y = a;<br />}<br />}<br />

假設對於Test的某個執行個體,有兩個thread分別執行task1(thread A)和task2 (thread B)。
由於兩個thread之間沒有任何的同步,所以可能出現多種執行情況。但更特別的是,由於JVM的最佳化,存在的亂序的可能,所以有可能出現:
thread A:     x = b -----------------------------------------> a = 1
thread B:     ----------->b =1 -------> y = a ---------------------
最後,x=0,y=0
關於這一點,就不多說了。


現在的問題是,如果改變一下代碼,變成:

public class Test {<br />int x = 0, y = 0;<br />int a = 0, b = 0;<br />public void task1() {<br />a = 1;<br />synchronized (this) {<br />x = b;<br />}<br />}<br />public void task2() {<br />synchronized (this) {<br />b = 1;<br />}<br />y = a;<br />}<br />}<br />


那麼,是否還有可能出現:
thread A:     synchronized{x = b;} -------------------------------------------------------------> a = 1
thread B:     ----------------------------------------------> synchronized{b = 1;}--> y = a ----------


結論是不可能再出現了。



Java Concurrency in Practice的3.1.3中有這麼一段:

When thread A executes a synchronized block, and subsequently thread B enters a synchronized block guarded by the same lock, the values of variables that were visible to A prior to releasing the lock are guaranteed to be visible to B upon acquiring thelock.

In other words, everything A did in or prior to a synchronized block
is visible to B when it executes a synchronized block guarded by the same lock. Without synchronization, there is no such guarantee.

.......

Locking is not just about mutual exclusion; it is also about memory visibility.


意思是,如果thread A進入了synchronized區,然後thread B接著進入,那麼,在thread B擷取這個lock的時候,thread A的synchronized區和之前的代碼的執行結果對於thread B來說,都是可見的,:


放在我們的例子中,假設Thread A先執行,先進入synchronized地區,那麼當thread B後進入synchronzied地區,準備執行 b = 1 時,JVM保證thread A的
      synchronized (this) {x = b;}
這個代碼執行效果對於thread B是可見的,不僅如此,連synchronzied地區之前的代碼
      a = 1;
也必須是可見的,所以,如果是thread A先進入synchronized地區的話,那麼只能是:

thread A:     synchronized{x = b;} ----> a = 1 ------------------------------------------------------
thread B:     -------------------------- -----------------------> synchronized{b = 1;}----> y = a ---

或者

thread A:     a = 1 ----> synchronized{x = b;} --------------------------------------------------------
thread B:     ----------------------------------------------------> synchronized{b = 1;}----> y = a ---

它們的結果都是 x = 0, y = 1。


有了這個結論,再回到本文最前面的我報bug的那個例子。可以知道那個NullPointException並不是由於所謂的“亂序執行”所導致的,應該是程式其它某個地方沒有寫好。所以,之前所謂的“Java Memory Model引發的血案”是不成立的,退堂。(對不住各位看官了....)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.