天天看點

設計模式——單例模式的分析與選擇一、簡介二、常用單例模式分析三、結論

一、簡介

單例模式:不能自由構造對象、在應用中隻存在一個執行個體的情況。

例如:圖檔加載器中一般都含有這麼幾個部分:RequestManager(請求管理器)、線程池、Engine(資料擷取引擎)、MemoryCache、DiskLRUCache、Transformation(圖檔處理)、Target等子產品,很消耗資源,沒有必要也不應該建立多個執行個體。

寫法有多種,下面分析常用的懶漢單例模式的優缺點,并做出最優選擇。

二、常用單例模式分析

1. 傳統懶漢模式

代碼:

public class Singleton1 {
    private static Singleton1 sInstance;

    private Singleton1() {
    }

    public static synchronized Singleton1 getInstance() {
        if (sInstance == null) {
            sInstance = new Singleton1();
        }
        return sInstance;
    }
}
           

 * 優點:第一次調用getInstance()時才初始化,synchronized保證線程安全

 * 缺點:每次調用getInstance()時都會同步,即使sInstance已經初始化過了,造成不必要同步開銷

 * 結論:不推薦

2. DCL(Double Check Lock)

代碼:

public class Singleton2 {
    private static Singleton2 sInstance;

    private Singleton2() {
    }

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

getInstance()方法進行了兩次判空,第一次是為了避免不必要的同步,第二次則是在null的情況下建立執行個體。

看起來很好但是存在問題:因為sInstance = new Singleton2()這句不是原子操作(可能會被打斷),它可分為3步:

  1. 給sInstance配置設定記憶體
  2. 調用構造函數初始化成員變量
  3. 将sInstance指向配置設定的記憶體空間(執行完本操作後,sInstance != null)

假設:線程A執行到sInstance = new Singleton2()這步,走的順序為1->3->2,當執行完3後,線程B直接取走了非空的sInstance(但并沒有初始化),使用時就會出錯,導緻DCL(Double Check Lock)失效

* 優點:懶加載,第一次執行getInstance()時單例對象才會執行個體化,資源使用率高、效率高;

 * 缺點:高并發時有風險(小機率發生錯誤);第一次加載稍慢

 * 結論:能滿足絕大部分使用場景,推薦使用

3.DCL改善

代碼:

public class Singleton3 {
    private volatile static Singleton3 sInstance;

    private Singleton3() {
    }

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

在DCL基礎上,将sInstance設為volatile

volatile:1.保證線程本地不會有變量副本,每次都從主記憶體中讀取;2.保證變量的寫操作都先行發生在後面對于它的讀操作

 * 優點:每次都從主記憶體中讀取sInstance,将DCL中sInstance = new Singleton2()操作原子化,避免錯誤

 * 缺點:多少影響一點性能

 * 結論:可以選擇

4. 靜态内部類單例

代碼:

public class Singleton4 {
    private Singleton4() {
    }

    private static class Holder {
        private static final Singleton4 INSTANCE = new Singleton4();
    }

    public static Singleton4 getInstance() {
        return Holder.INSTANCE;
    }
}
           

* 優點:懶加載、線程安全、保證單例對象的唯一性(靜态内部類隻會被加載一次)

* 缺點:無

* 結論:最佳選擇

5. 枚舉單例

代碼:

public enum Singleton5 {
    INSTANCE
}
           

* 優點:寫法簡單

* 缺點:枚舉比較占資源,官方不是很推薦

* 結論:不是很推薦

三、結論

最佳選擇:  4. 靜态内部類單例

第二選擇:2. DCL

第三選擇:3. DCL改善

(個人看法)

繼續閱讀