簡體   English   中英

有關單例類和線程的問題

[英]question about singleton classes and threads

我正在嘗試了解單例類,以及如何在應用程序中使用它們以確保其線程安全。 假設您有一個稱為IndexUpdater的單例類,其引用如下獲得:

 public static synchronized IndexUpdater getIndexUpdater() {
    if (ref == null)
        // it's ok, we can call this constructor
        ref = new IndexUpdater();
    return ref;
}

private static IndexUpdater ref;

假設類中還有其他方法可以完成實際工作(更新索引等)。 我想了解的是如何使用兩個線程來訪問和使用單例。 假設在時間1中,線程1通過像這樣的調用獲得對類的引用:IndexUpdater iu = IndexUpdater.getIndexUpdater(); 然后,在時間2中,使用引用iu,線程1將類中的方法稱為iu.updateIndex。在時間2中會發生什么,第二個線程嘗試獲取對該類的引用。 它可以做到這一點,也可以訪問單例內的方法嗎?或者,只要第一個線程對該類有有效的引用,就可以阻止它。 我假設使用后者(否則它將如何工作?),但我想在實施之前先確定一下。

謝謝,

艾略特

您的假設是錯誤的。 同步getIndexUpdater()僅防止(幾乎)同時調用getIndexUpdater()的不同線程創建多個實例。

沒有同步,可能會發生以下情況:線程一調用getIndexUpdater()。 ref為空。 線程2調用getIndexUpdater()。 ref仍然為null。 結果:ref被實例化兩次。

由於getIndexUpdater()是同步方法,因此它僅防止線程同時訪問此方法(或由同一同步器保護的任何方法)。 因此,如果其他線程同時訪問該對象的方法,則可能會出現問題。 請記住,如果線程正在運行同步方法,則所有其他試圖在同一對象上運行任何同步方法的線程都將被阻止。

有關更多信息: http : //download.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html

您正在將單例對象的實例化與其使用混淆。 同步創建單例對象並不能保證單例類本身是線程安全的。 這是一個簡單的示例:

public class UnsafeSingleton {
    private static UnsafeSingleton singletonRef;
    private Queue<Object> objects = new LinkedList<Object>();

    public static synchronized UnsafeSingleton getInstance() {
        if (singletonRef == null) {
            singletonRef = new UnsafeSingleton();
        }

        return singletonRef;
    }

    public void put(Object o) {
        objects.add(o);
    }

    public Object get() {
        return objects.remove(o);
    }
}

保證兩個調用getInstance線程都可以獲取UnsafeSingleton的相同實例,因為同步此方法可以保證singletonRef僅被設置一次。 但是,返回的實例不是線程安全的 ,因為(在此示例中) LinkedList不是線程安全的隊列。 兩個線程修改此隊列可能會導致意外行為。 必須采取其他步驟來確保單例本身是線程安全的,而不僅僅是其實例化。 (例如,在此示例中,可以用LinkedBlockingQueue替換隊列實現,或者可以將getput方法標記為synchronized 。)

然后,在時間2中,使用引用iu,線程1將類中的方法稱為iu.updateIndex。在時間2中會發生什么,第二個線程嘗試獲取對該類的引用。 它可以做到這一點,並且可以訪問單例內的方法嗎?

答案是肯定的。 您關於如何獲得引用的假設是錯誤的。 第二個線程可以獲得對Singleton的引用。 Singleton模式最常用作一種偽全局狀態。 眾所周知,當多個實體使用全局狀態時,通常很難處理它。 為了使您的單例線程安全,您將需要使用適當的安全機制,例如使用原子包裝器類(例如AtomicIntegerAtomicReference等)或使用synchronize (或Lock )來保護關鍵代碼區域免於同時訪問。

最安全的方法是使用枚舉。

public enum Singleton {
  INSTANCE;
  public String method1() {
    ...
  }
  public int method2() {
    ...
  }
}

線程安全,可序列化,延遲加載等。只有優點!

當第二個線程嘗試調用getIndexUpdater()方法時,它將嘗試獲取所謂的lock ,它是在您使用synchronized關鍵字時為您創建的。 但是由於方法中已經有其他線程,因此它較早獲得了鎖,而其他線程(如第二個線程)必須等待它。

當第一個線程完成其工作時,它將釋放鎖,第二個線程將立即獲取該鎖並進入方法。 綜上所述,使用synchronized總是只允許一個線程進入受保護的塊-非常嚴格的訪問。

靜態同步保證了一次只能有一個線程進入該方法,並且任何其他嘗試訪問此方法的線程(或此類中的任何其他靜態同步方法)都必須等待它完成。

恕我直言,實現單例的最簡單方法是讓枚舉具有一個值

enum Singleton {
    INSTANCE
}

這是線程安全的,並且僅在訪問該類時創建INSTANCE。

一旦您的同步getter方法將返回IndexUpdater實例(無論它是剛剛創建還是已經存在都沒有關系),就可以從另一個線程中調用它。 您應該確保IndexUpdater是線程安全的,以便可以一次從多個線程中調用它,或者應該為每個線程創建一個實例,以使它們不會被共享。

暫無
暫無

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

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