簡體   English   中英

以下 java 代碼線程安全且無 volatile 嗎?

[英]Is the following java code thread safe without volatile?

public static Singleton singleton;

public static Singleton get(){
    synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
    } 
    return singleton;
}

有人說singleton變量沒有 volatile 是錯誤的。 但我認為這是 singleton object 創建的正確代碼。 我想知道這段代碼是否具有線程安全性?

正如anatolyg指出的那樣,您應該將字段singleton私有,以避免對該字段進行不必要的非線程安全訪問。

此外,即使在:

public static Singleton get(){
    synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
    } 
    return singleton;
} 

return singleton; synchronized塊之外,此代碼仍然是線程安全的,因為其余代碼在synchronized塊內,因此,該塊內的所有線程將強制發生先發生關系(即,線程無法返回 null如果實例設置正確)。

話雖如此,請注意:引用Holger

只要實際寫入 singleton 發生在同步塊開始之前,一切正常。 它之所以有效,是因為最多只有一次 write ,肯定是在返回之前執行的。 如果可以進行更多寫入,則它們可能會同時發生在同步塊之外的 return 語句中。

有一個完整的SO 線程可以解決為什么將return singleton留在synchronized塊之外是線程安全的。

盡管如此,我與其他用戶(例如這個用戶)有着相同的看法

由於返回不占用任何 CPU 或任何東西,因此沒有理由不應該在同步塊內。 如果是,那么如果我們在 Singleton class 中,則該方法可以標記為同步。 如果 singleton 在其他地方被修改,這將更清潔和更好。

話雖如此,您不需要volatile子句,因為您正在同步變量singleton的初始化。 在這種情況下, synchronized子句不僅保證多個線程不會訪問:

    if (singleton == null) {  
        singleton = new Singleton();  
    }  

而且每個線程都會看到singleton字段的最新參考。 因此,多個線程將不同的 object 實例分配給singleton字段的競爭條件不會發生。

有人說 singleton 變量沒有 volatile 是錯誤的。

可能這個人將您的代碼誤認為是雙重檢查鎖定模式,這是對您展示的版本的性能優化。 在您的版本中,線程將在每次調用get方法時進行同步,在變量singleton已正確初始化后,這不是必需的。 這是雙重檢查鎖定模式試圖避免的開銷。 為了實現需要volatile (您可以閱讀有關此SO Thread的深入解釋),可以在此處找到有關此雙重檢查鎖定模式的更多信息。

幾乎。 如果您使用synchronized塊對變量/字段進行線程安全訪問,則如果您在同一鎖下進行讀取和寫入(在同一對象的監視器上同步),則代碼是正確的。 因此,在您的代碼中,您應該防止“ private ”修飾符而不是“public”聲明讀取正常(沒有任何 memory 障礙)。

private static Singleton singleton; // now we don't have direct access (to read/write) the field outside

public static Singleton get(){
    synchronized (Singleton.class) { // all reads in the synchronized Happens-Before all writes
        if (singleton == null) { // first read
            singleton = new Singleton(); // write
        }
    } 
    return singleton; // the last normal read
}

您可能會注意到最后一次讀取return singleton; 是正常的,但它是在第一次同步讀取(如果 singleton 不為空)或寫入(如果為空)之后的后續(按程序順序)讀取,並且不需要放置在同步塊內,因為 PO->HB對於一個單線程( https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5 “如果 x 和 y 是同一個線程的動作並且 x 來了按程序順序在 y 之前,然后是 hb(x, y)")。

但從我的角度來看,以下結構似乎更慣用

// all access is under the lock
synchronized (Singleton.class) {  
    if (singleton == null) {
        singleton = new Singleton();
    }
    return singleton;
 }

要不就

public static synchronized Singleton get() {
    if (singleton == null) {
        singleton = new Singleton();
    }
    return singleton;
}

現在代碼是正確的,但它可能沒有那么高效。 這將我們帶到了雙重檢查鎖定習語。 您可以在https://shipilev.net/blog/2014/safe-public-construction/中找到對問題的良好回顧

順便說一句,有時甚至以下代碼也可以:

private static volatile Singleton singleton;

public static Singleton get() {
    if (singleton == null) {
        singleton = new Singleton();
    }
    return singleton;
}

此代碼沒有數據競爭,而是競爭條件。 它總是返回一個 Singleton 的實例,但並不總是相同的。 換句話說,在get()的第一次調用中,我們可能會看到返回的 Singleton 的不同實例,它們相互重寫到singleton字段,但如果你不在乎:)...(小的不可變/讀取- 僅具有相同 state 的單例,例如)

您的代碼中的變量不需要volatile 。但是您的代碼存在一些性能缺陷。 每次get中的代碼都會在synchronized塊內運行,這會導致很少的性能開銷。 您可以使用雙重檢查鎖定機制來避免性能開銷。 下面的代碼顯示了使用雙重檢查鎖定機制在 java 中創建線程安全 singleton 的正確方法。

  class Singleton {
    private static volatile Singleton singleton = null;
    public static Singleton get() {
        if (singleton == null) {
            synchronized(this) {
                if (singleton == null)
                    singleton = new Singleton();
            }
        }
        return singleton;
    }
}

有關更多詳細信息,請訪問此鏈接並滾動到底部“使用 Volatile 修復雙重檢查鎖定”部分。

暫無
暫無

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

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