設計模式 —— 單例模式(Singleton)

單例模式(Singleton)

概念:

單例模式 確保一個類只有一個實例,並提供一個全局訪問點。

單例模式


經典單例:

public class Singleton {
    private static Singleton uniqueInstance;
    //構造函數設定爲 private,避免被創建
    private Singleton() {}

    public static Singleton getInstance() {
        if (uniqueInstance == null) {
            //延遲初始化
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

延遲初始化雖然能避免資源浪費的情況,但是上面的單例模式在多線程情況下就可能出現錯誤,多線程同時執行到 if 語句時,可能會出現 new 兩個單例的情況。


不使用延遲初始化:

public class Singleton {
    private static Singleton uniqueInstance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return uniqueInstance;
    }
}

非延遲初始化的做法在 JVM 加載這個類時馬上創建唯一的單例。JVM 保證任何線程訪問 uniqueInstance 靜態變量之前,一定先創建此實例。
但如果程序中並沒有用到單例,提前創建好很可能會浪費資源。


多線程單例模式:

public class Singleton {
    private static Singleton uniqueInstance;

    private Singleton() {}
    //synchronized 保證多線程順序訪問
    public static synchronized Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

通過加入 synchronized 我們保證每個線程在訪問該方法時必須保證其他線程離開。保證了多線程情況下的安全。但 synchronized 同步可能會影響性能,在性能要求不苛刻的情況下我們可以選擇這種單例。


雙重檢查鎖單例模式

public class Singleton {
    //注意 volatile 是必須的,否則 JVM 可能會進行優化(指令重排)導致錯誤
    private volatile static Singleton uniqueInstance;

    private Singleton() {}

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

volatile 作用:這個變量不會在多個線程中存在複本,直接從內存讀取。這個關鍵字會禁止指令重排序優化。也就是說,在 volatile 變量的賦值操作後面會有一個內存屏障(生成的彙編代碼上),讀操作不會被重排序到內存屏障之前。

雙重檢查鎖 模式看着很完美,實際上還有些小問題,就是在早期版本下的 volatile 可能會導致該方式失敗。


優雅版本:

public class Singleton {
    //靜態內部類,
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton() {}

    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

只有在調用 getInstance 時對象纔會被創建,同時沒有性能缺點,也不依賴 Java 版本。


枚舉單例:

public enum Singleton {
    INSTANCE;
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

優點:非常簡單。
默認枚舉實例的創建是線程安全的,所以不需要擔心線程安全的問題


適用場景:

  • 系統只需要一個實例對象,如系統要求提供一個唯一的序列號生成器,或者需要考慮資源消耗太大而只允許創建一個對象。
  • 客戶調用類的單個實例只允許使用一個公共訪問點,除了該公共訪問點,不能通過其他途徑訪問該實例。
  • 在一個系統中要求一個類只有一個實例時才應當使用單例模式。反過來,如果一個類可以有幾個實例共存,就需要對單例模式進行改進,使之成爲多例模式

優缺點:

優點:

  • 提供了對唯一實例的受控訪問。因爲單例類封裝了它的唯一實例,所以它可以嚴格控制客戶怎樣以及何時訪問它,併爲設計及開發團隊提供了共享的概念。
  • 由於在系統內存中只存在一個對象,因此可以節約系統資源,對於一些需要頻繁創建和銷燬的對象,單例模式無疑可以提高系統的性能。
  • 允許可變數目的實例。我們可以基於單例模式進行擴展,使用與單例控制相似的方法來獲得指定個數的對象實例。

缺點:

  • 由於單例模式中沒有抽象層,因此單例類的擴展有很大的困難。
  • 單例類的職責過重,在一定程度上違背了“單一職責原則”。因爲單例類既充當了工廠角色,提供了工廠方法,同時又充當了產品角色,包含一些業務方法,將產品的創建和產品的本身的功能融合到一起。
  • 濫用單例將帶來一些負面問題,如爲了節省資源將數據庫連接池對象設計爲單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;現在很多面嚮對象語言(如Java、C#)的運行環境都提供了自動垃圾回收的技術,因此,如果實例化的對象長時間不被利用,系統會認爲它是垃圾,會自動銷燬並回收資源,下次利用時又將重新實例化,這將導致對象狀態的丟失。

參考:

Head Frist 設計模式

單例模式

深入淺出單實例Singleton設計模式

發佈了144 篇原創文章 · 獲贊 70 · 訪問量 44萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章