[英]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.