簡體   English   中英

同步塊后讀取的可見性

[英]Visibility of a read after synchronized block

JMM 是否保證在synchronized塊之后對在另一個線程中讀取的變量進行synchronized寫入的可見性? 這就是我的意思:

public class SynchronizedWriteRead {

    private int a;
    private int b;

    public void writer() {
        synchronized (this) {
            a = 5;
            b = 7;
        }
    }

    public void reader() {
        synchronized (this) {
            int r1 = a; /* 5 */
        }
        int r2 = b; /* ? */
    }
}

JMM 保證監視器上的解鎖發生在該監視器上的每個后續鎖定之前。 但我不確定它是否僅與synchronized塊體有關。

最近我在 Java 中遇到了 Aleksey Shipilëv 的這篇文章 - 安全發布和安全初始化 它說:

請注意,在Unsafe DCL存儲中進行synchronized是如何無濟於事的,這與外行人的看法相反,它以某種方式神奇地“刷新了緩存”或諸如此類的東西。 在讀取受保護的 state 時,如果沒有配對鎖,則無法保證在鎖保護寫入之前看到寫入。

所以這就是我問自己這個問題的原因。 我在JLS中找不到答案。

讓我們換一種說法。 有時你會捎帶一個volatile發生之前的保證,如下所示:

public class VolatileHappensBefore {

    private int a; /* specifically non-volatile */
    private volatile int b;

    public void writer() {
        a = 5;
        b = 7;
    }

    public void reader() {
        int r1 = b; /* 7 */
        int r2 = a; /* 5 */
    }
}

因為同一線程中的順序操作由happens-before 支持,並且happens-before 本身是可傳遞的,所以您可以保證看到這兩種寫入。

我可以以相同的方式使用synchronized發生之前的保證嗎? 甚至可能像這樣(我已經放置了sync變量以禁止編譯器/JVM刪除其他空的synchronized塊):

    public void writer() {
        a = 5;
        b = 7;
        synchronized (this) {
            sync = 1;
        }
    }

    public void reader() {
        synchronized (this) {
            int r = sync;
        }
        int r1 = a; /* ? */
        int r2 = b; /* ? */
    }

你現在已經得到了答案,公平地說,這里的每個人都是正確的,我只想添加一個重要的規則。 它被稱為“在一致性之前發生”,它是這樣的:

  • 讀取要么看到發生在發生前順序中的最新寫入,要么看到任何其他未按照發生前發生順序寫入的寫入,因此是數據競爭。

因此,雖然公認的答案確實是正確的,但應該提到,為了在(3)(4)之間創建發生前的邊緣, (4)必須觀察(3)所做的寫入。

在您的示例中:

public void reader() {
   synchronized (this) {
      int r = sync;
   }
   int r1 = a; /* ? */
   int r2 = b; /* ? */

}

這意味着int r = sync; 不正確,您需要斷言您實際上看到sync1 (您已觀察到寫入)。 例如,這將創建所需的邊緣:

if (sync == 1) {
    // guaranteed that r1=5 and r2=7
    int r1 = a;
    int r2 = b;
}

JMM 是否保證在synchronized塊之后對在另一個線程中讀取的變量進行synchronized寫入的可見性?

我可以以相同的方式使用同步發生之前的保證嗎?

是的,是的。

synchronizedvolatile提供了相同的可見性保證。

事實上,一個volatile變量的行為就好像這個變量的每次讀取和寫入都發生在它自己的小synchronized塊中。

JLS條款中:

  • 可見性由happens-before關系保證:

    非正式地,如果沒有發生之前的命令以防止讀取,則允許讀取r查看寫入w的結果。

  • volatilesynchronized是建立前發生關系的一些方法:
    • 監視器上的解鎖發生在該監視器上的每個后續鎖定之前
    • 對 volatile 字段(第 8.3.1.4 節)的寫入發生在對該字段的每次后續讀取之前
    • ...

Java 中安全發布和安全初始化的引用描述了以下情況:

  • object 在一個線程的synchronized塊中初始化
  • object 並且在另一個線程中沒有synchronized塊的情況下讀取。

在這種情況下,閱讀器線程可能會在初始化過程中看到 object。

這里需要注意的重要一點是, happens-before是一個傳遞關系。 因此,如果A發生在BB發生在C之前,我們可以安全地得出A發生在C之前的結論。

現在讓我們看看有問題的代碼(為了清楚起見,我添加了注釋):

public void writer() {
    a = 5; //1
    b = 7; //2
    synchronized (this) { 
        sync = 1;
    } //3
}

public void reader() {
    synchronized (this) { //4
        int r = sync;
    }
    int r1 = a; //5 
    int r2 = b; //6
}

我們知道1發生在2之前和2發生在3之前,因為它們是由同一個線程執行的:

如果 x 和 y 是同一線程的操作,並且 x 在程序順序中位於 y 之前,則為 hb(x, y)。

我們也知道3發生在4之前:

監視器 m 上的解鎖操作與 m 上的所有后續鎖定操作同步(其中“后續”根據同步順序定義)。

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

最后,我們知道4發生在5之前和5發生在6之前,因為它們在同一個線程中執行。

如果 x 和 y 是同一線程的操作,並且 x 在程序順序中位於 y 之前,則為 hb(x, y)。

所以我們最終得到了從 1 到 6 的一系列發生之前的關系。因此, 1發生在6之前。

JMM 是否保證在同步塊之后對在另一個線程中讀取的變量進行同步寫入的可見性?

是的,由於其他答案中給出的原因。

但是有一個問題!

讓我們假設reader調用跟在writer調用之后,因此在writer退出其同步塊和 reader 進入其塊之間之前發生了一個事件......並傳遞它退出塊並讀取b

問題是當reader退出其塊時,不再保證b上的互斥。 所以假設另一個線程立即獲取互斥鎖並修改b 由於沒有 HB 鏈寫入b與 read in reader連接起來,因此無法保證reader代碼實際看到更新...或writer之前寫入的值。

簡而言之,在推理並發算法時,需要考慮的不僅僅是HB 關系。

暫無
暫無

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

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