1. 程式人生 > >單例模式筆記

單例模式筆記

加鎖 color ron num ssi 方法 判斷 順序 list

定義

單例模式是限制類的實例只有一個的設計模式。

代碼

單線程下的單例模式代碼

public class SimpleSingleton {
    private static SimpleSingleton simpleSingleton;
?
    private SimpleSingleton() {}
?
    public static SimpleSingleton getInstance() {
        // 如果 simpleSingleton 未進行實例化,則創建實例,之後都使用此實例
        if (simpleSingleton == null) {
            simpleSingleton 
= new SimpleSingleton(); } return simpleSingleton; } }

但是如果在多線程的情況下,可能出現在第一個線程 A 線程判斷simpleSingleton為空時,進入simpleSingleton = new Simpleton();部分進行代碼實例化,但在 A 線程還未實例化結束時,另一個線程 B 線程進行判斷simpleSingleton是否為空,此時由於 A 線程還沒有實例化賦值給simpleSingleton,所以其仍未null,這是 B 線程也會進入執行simpleSingleton = new Simpleton();

,這樣就會導致出現不同的實例,所以這種方法只能用在單線程的情況下。

多線程下的單例模式代碼

多線程下最簡單的方式是對getInstance()方法整個加上synchronized修飾符,保證每次只有一個線程能進入此方法。但是這樣做會很影響性能,我們應該盡量減小鎖作用的範圍,所以最好采用如下的雙重加鎖方式:

public class SynchronizedSingleton {
?
    private static SynchronizedSingleton singleton;
?
    private SynchronizedSingleton() {}
?
    public static
SynchronizedSingleton getInstance() { if (singleton == null) { synchronized (SynchronizedSingleton.class) { if (singleton == null) { singleton = new SynchronizedSingleton(); } } } return singleton; } }

之所以進行第二次singleton == null判斷是因為在線程 A 在第一次進行判斷為null後獲得鎖進行實例化,在實例化未完成時,B 線程判斷仍為null,這是由於獲得不了鎖,所以等待,在 A 線程創建實例後釋放了鎖,這時 B 線程獲得鎖並執行,如果此時不進行第二次的singleton == null判斷,則 B 線程也會創建一個新的實例,導致單例模式出現問題;而如果進行第二次判斷,則會得知singleton已經被實例化,就不會再創建新的實例。

目前一切看起來都很美好,但是仍然有一個問題需要解決。

創建對象實例可以分為三個步驟:

  1. 分配內存

  2. 調用構造函數

  3. 將對象指向分配的內存地址

之前的代碼如果依此順序執行,則不會有問題。但是為了提高性能,編譯器和處理器常常會對指令進行重排序,這時如果步驟 2 和步驟 3 的順序顛倒了,先將對象指向分配的內存地址,後執行構造函數那麽就會出現問題。當 A 線程將對象指向分配的內存地址,但還未執行構造函數的時候, B 線程進入,判斷對象不為空,則將對象引用返回,這時如果使用此引用,則會出現問題。

目前有三個解決方法:

1. 給靜態實例屬性加上 volatile關鍵字(需要 JDK 1.5 及之後版本)
private static volatile SynchronizedSingleton singleton;

volatile 關鍵字可以保證對 volatile 變量的操作不會進行重排序。

2. 使用單個元素的枚舉類型(需要 JDK 1.5 及之後版本)
public enum Singleton {
    INSTANCE;
}

3. 使用子類,由 JVM 保證單例
public class InnerClassSingleton {
    public static Singleton getInstance() {
        return Singleton.singleton;
    }
?
    private static class Singleton {
        static Singleton singleton = new Singleton();
    }
}

類的靜態屬性只會在第一次加載的時候初始化一次,同時 JVM 保證在初始化的過程中(未完成時)無法被使用。

單例模式筆記