1. 程式人生 > >6種單例模式實現

6種單例模式實現

普通懶漢式

public class Singleton {

    /** 單例物件 */
    private static Singleton instance;

    /**
     * 私有構造方法.
     */
    private Singleton() {
    }

    /**
     * 靜態方法, 用於獲取單利物件.
     * 如果單例物件未建立, 則建立新單例物件, 否則直接返回該物件.
     *
     * @return 單例物件.
     */
    public static Singleton getInstance() {
        if
(instance == null) { instance = new Singleton(); } return instance; } }

最簡單的懶漢式單例,在首次呼叫 getInstance(); 時,會對單例物件進行例項化。
然而,這種方式明顯無法在多執行緒模式下正常工作。當執行緒併發呼叫getInstance(); 時,由於執行緒之間沒有進行同步,有可能兩個執行緒同時進入 if 條件,導致例項化兩次。

執行緒安全的懶漢式

public class Singleton {

    /** 單例物件 */
private static Singleton instance; /** * 私有構造方法. */ private Singleton() { } /** * 靜態方法, 用於獲取單利物件. * 如果單例物件未建立, 則建立新單例物件, 否則直接返回該物件. * * @return 單例物件. */ public static synchronized Singleton getInstance() { if (instance == null) { instance = new
Singleton(); } return instance; } }

最簡單的執行緒安全的懶漢模式,通過在 getInstance() 方法上新增 synchronized 關鍵字,保證同一時間僅有一個執行緒能夠執行該程式碼段,以保證不會出現上面一種方法產生的問題。
然而,這種方法效率很低。每次呼叫 getInstance() 方法,都將為程式碼段加鎖,同一時間該程式碼段只能被一個執行緒訪問。然而除了首次呼叫外,都是不需要同步的,因為 instance 已經被例項化。

Double-Check

public class Singleton {

    /** 單例物件 */
    private static Singleton instance;

    /**
     * 私有構造方法.
     */
    private Singleton() {
    }

    /**
     * 靜態方法, 用於獲取單利物件.
     * 如果單例物件未建立, 則建立新單例物件, 否則直接返回該物件.
     *
     * @return 單例物件.
     */
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Double-check 即雙重校驗,該方法是針對上述方法提出的一種改進方案。
在 getInstance() 方法中,通過不加鎖判斷 instance 是否例項化。如果沒有例項化,再進行加鎖、例項化過程,以減少在例項化後呼叫 getInstance() 方法導致的效能損耗。

缺陷:instance = new Singleton()語句,看起來是一句程式碼,但實際上不是一個原子操作。它大致做了三件事:
1)給Singleton的例項分配記憶體
2)呼叫建構函式,初始化成員欄位
3)將insance物件指向分配的記憶體空間
但是由於Java編譯器允許處理器亂序執行,以及各種其他情況,在JDK1.5前上面的第二步和第三步沒有辦法保證執行順序。當執行順序是1-3-2時,如果執行到3後還沒執行2.這時候有另一個執行緒呼叫了getInstance()方法,那麼它判斷到的instance不是null,它不需要經過同步程式碼塊,直接獲取到了這個錯誤的物件去做事去了。這就導致了錯誤出現。解決方法是private static Singleton instance;改為private static volatile Singleton instance;以及使用JDK1.5以上的版本。建議還是使用靜態內部類的方式更好,可以規避上述問題

餓漢式

public class Singleton {

    /** 單例物件, 類裝載時進行例項化. */
    private static final Singleton singleton = new Singleton();

    /**
     * 私有構造方法.
     */
    private Singleton() {
    }

    /**
     * 靜態方法, 用於獲取單利物件.
     *
     * @return 單例物件.
     */
    public static Singleton getInstance() {
        return singleton;
    }
}

餓漢式單例的原理是 ClassLoader 裝載類是單執行緒,通過這種機制避免了執行緒同步問題。
這種方式雖然避免了執行緒同步問題,但卻有可能帶來效能問題。
無論該類是否被使用, ClassLoader 都有可能(也有可能被 ClassLoader 忽略)載入該類並例項化該單例物件。所以在基礎類庫場景下,這種方法會無故消耗更多的資源。

靜態內部類方式

public class Singleton {

    /**
     * 私有構造方法.
     */
    private Singleton() {
    }

    /**
     * 靜態方法, 用於獲取單利物件.
     *
     * @return 單例物件.
     */
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }

    private static class SingletonHolder {

        /** 單例物件, 類裝載時進行例項化. */
        private static final Singleton instance = new Singleton();
    }
}

這種方法同樣利用了 ClassLoader 單執行緒裝載的方式,避免了執行緒同步問題。然而他和上面一種方法不同的地方在於, instance 物件只有在 SingletonHolder 類被裝載的時候才會被例項化。也就是說,只有當 getInstance() 方法呼叫時,才會被例項化,這樣就避免了上述的資源損耗。

列舉方式

public enum Singleton {
   INSTANCE;
 }