簡體   English   中英

volatile,final和synchronized之間安全發布的差異

[英]Differences in safe publishing between volatile,final and synchronized

給定具有變量x的A類。 變量x在類構造函數中設置:

A() {
x = 77;
}

我們想將x發布到其他一些線程。 考慮以下3個變量x線程安全(?)發布的情況:

1)x是最終的

2)x是易變的

3)x在同步塊中設置

synchronized(someLock) {
A a  = new A();
a.x = 77;
}

Thread2只打印x:

 System.out.println(a.x);

問題是:是否可以觀察到Thread2打印的'0'? 或者JMM保證將打印'77'或者在所有3種情況下都會拋出NPE?

我的答案來自互聯網上最好的頁面 ,特別是第17章涉及內存可見性和並發性。

我還假設您沒有引用泄漏(即,在對象構造函數完成之前,您沒有對該對象的引用)。

  • 最后的領域。 我將在第17.5章引用上面的內容:

    當構造函數完成時,對象被認為是完全初始化的。 在該對象完全初始化之后只能看到對象引用的線程可以保證看到該對象的最終字段的正確初始化值。

  • 揮發性。 我再次引用JLS:

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

因此,假設您的Thread在完成構造函數后可以訪問該對象,它將看到正確的值。 注意,這意味着你可能也需要制作一個易變的。

  • x在同步塊中。 這是一個棘手的問題。 它可能會也可能不會顯示。 事實上,同步只會稍微增加解釋這個問題的難度,所以我會刪除它並解釋是否會在這里看到一個簡單的局部變量。 然后添加一個關於synchronized的子句。

根據定義,如果讀取和寫入之間存在Happens-before關系,則保證可見。 否則,您可能會看到未初始化的值。 什么構成了先Happens-before關系? 同樣,JLS第17章規定了這一點,特別是:

  • 單個線程中的操作順序。
  • 同步,鎖定和波動。
  • 對象和線程創建。
  • 一切都是傳遞性的。
  • 更多內容,請閱讀JLS

因此可能有兩種情況:

A a = new A();
Thread t = new MyThread(a);
t.start();

MyThread保存A的實例並使用它的地方。 在這種情況下,線程在a之后創建,並在創建后調用start() 因此即使x是非易失性的,也能保證可見性。 但是,無法保證對x的進一步更改的可見性。

情況2:

編碼起來有點困難,但是:
Main創建兩個Thread並立即啟動它們,並且有一個A類型的非易失性字段。
ThreadA創建A並將其寫入共享字段。
ThreadB循環一段時間,直到填充字段然后打印出x。

在這種情況下,即使寫入x和寫入共享字段之間存在HB,共享字段的讀取和寫入之間也不存在HB。 因此,無法保證寫入x的可見性。

正如所承諾的那樣 - 如果在這兩種情況中的任何一種情況下在x的寫入周圍放置一個同步塊,它將不會影響結果,因為監視器上沒有其他任何鎖定。 鎖定和解鎖同一監視器會創建同步操作,從而創建HB關系。

暫無
暫無

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

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