簡體   English   中英

發生在與Java中的易失性字段和同步塊的關系之前 - 以及它們對非易失性變量的影響?

[英]Happens-before relationships with volatile fields and synchronized blocks in Java - and their impact on non-volatile variables?

我對線程概念還很陌生,並嘗試更多地了解它。 最近,我發現了一篇關於Jeremy Manson撰寫的“ Java中易失手段”的博客文章,他寫道:

當一個線程寫入一個volatile變量,而另一個線程看到該寫入時,第一個線程告訴第二個線程關於內存的所有內容,直到它執行對該volatile變量的寫入。 [...]線程1在寫入[volatile] ready之前看到的所有內存內容必須對線程2可見,在讀取值為true后才ready [自己強調]

現在,這是否意味着在寫入volatile變量時,線程1的內存中保存的所有變量(volatile或not)將在讀取volatile變量后變為可見? 如果是這樣, 是否有可能從官方Java文檔/ Oracle源代碼中將該語句拼湊起來? 從哪個版本的Java開始這個工作?

特別是,如果所有線程共享以下類變量:

private String s = "running";
private volatile boolean b = false;

並且線程1首先執行以下操作:

s = "done";
b = true;

然后線程2執行(在線程1寫入volatile字段之后):

boolean flag = b; //read from volatile
System.out.println(s);

這會保證打印“完成”嗎?

如果不將b聲明為volatile而是將寫入和讀取放入synchronized塊,會發生什么?

另外,在題為“ 線程之間是否共享靜態變量? ”的討論中,@ TREE 寫道

不要使用volatile來保護多個共享狀態。

為什么? (對不起;我還沒有就其他問題發表評論,或者我會在那里問過......)

是的,保證線程2將打印“完成”。 當然,這是因為線程1中對b的寫入實際上發生在線程2中從b讀取之前,而不是在同一時間或之前發生!

這里推理的核心是先發生過的關系 多線程程序執行被視為由事件組成。 事件可以通過發生在之前的關系來關聯,這表示一個事件發生在另一個事件之前。 即使兩個事件沒有直接關聯,如果你可以追蹤從一個事件到另一個事件的一系列發生在前的關系,那么你可以說一個事件發生在另一個事件之前。

在您的情況下,您有以下事件:

  • 線程1寫入s
  • 線程1寫入b
  • 線程2從b讀取
  • 線程2從s讀取

以下規則發揮作用:

  • “如果x和y是同一個線程的動作,x在程序順序中出現在y之前,那么hb(x,y)。” 程序訂單規則)
  • “在對該字段的每次后續讀取之前,都會發生對易失性字段(第8.3.1.4節)的寫入。” 易變規則)

以下情況發生在 - 因此存在關系之前:

  • 線程1寫入s發生在線程1寫入b (程序順序規則)之前
  • 線程1寫入b發生在線程2從b讀取之前(易失性規則)
  • 線程2從b讀取線程2從s讀取之前(程序順序規則)

如果您遵循該鏈,您可以看到結果:

  • 線程1寫入s發生在線程2從s讀取之前

如果不將b聲明為volatile,而是將寫入和讀取放入同步塊,會發生什么?

當且僅當您使用相同的鎖保護所有此類同步塊時,您才能獲得與volatile示例相同的可見性保證。 此外,您還將相互排除此類同步塊的執行。

不要使用volatile來保護多個共享狀態。

為什么?

volatile不保證原子性:在你的例子中,在你顯示的寫入之后, s變量也可能被其他線程變異; 閱讀線程無法保證它看到的是哪個值。 在讀取volatile之后但在讀取s之前寫入s發生同樣的事情。

在實踐中做什么是安全的,並且在寫入volatile變量的引用中可以傳遞可共享的不可變狀態 所以也許這就是“一個共享國家”的意思。

是否可以從官方Java文檔/ Oracle源代碼中將該語句拼湊起來?

來自規范的行情:

17.4.4。 同步順序

對易失性變量v(第8.3.1.4節)的寫入與任何線程對v的所有后續讀取同步(其中“后續”根據同步順序定義)。

17.4.5。 發生在訂單之前

如果x和y是同一個線程的動作,並且x在程序順序中出現在y之前,那么hb(x,y)。

如果動作x與后續動作y同步,那么我們也有hb(x,y)。

這應該足夠了。

從哪個版本的Java開始這個工作?

Java語言規范,第3版引入了內存模型規范的重寫,這是上述保證的關鍵。 NB大多數以前的版本都表現得好像保證在那里,而且許多代碼行實際上依賴於它。 當人們發現保證實際上沒有存在時,人們感到很驚訝。

這會保證打印“完成”嗎?

Java Concurrency in Practice中所述

當線程A寫入volatile變量並且隨后線程B讀取相同的變量時, 在寫入volatile變量之前,A可見的所有變量的值在讀取volatile變量后變為B可見

是的 ,這保證了打印“完成”。

如果不將b聲明為volatile,而是將寫入和讀取放入同步塊,會發生什么?

這也將保證相同。

不要使用volatile來保護多個共享狀態。

為什么?

因為,volatile只保證可見性。 它不保證原子性。 如果我們在一個被線程A訪問的方法中有兩個易失性寫入而另一個線程B正在訪問那些易失性變量,那么當線程A正在執行該方法時,線程A可能會被線程B搶占。操作的中間(例如,在第一次易失性寫入之后但在線程A第二次易失性寫入之前)。 因此,保證操作synchronization的原子性是最可行的出路。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM