詳解 - 單例模式

Burjal本尊發表於2017-12-16

Android設計模式”這個系列主要是對Android專案中的設計模式進行分析總結,學習自《Android 原始碼設計模式解析與實戰》,錯誤之處煩請指正~


Android設計模式系列文章:

1、詳解 - 單例模式 2、詳解 - Builder模式


一、 概述

1.1 定義

確保某一個類只有一個例項,而且自行例項化並向整個系統提供這個例項。

1.2 使用場景

確保某個類有且只有一個物件的場景,避免產生多個物件消耗過多的資源;或者某種型別的物件應該有且只有一個。

eg:建立一個物件需要消耗的資源過多,如訪問IO和資料庫資源。

1.3 關鍵點

  • 建構函式不對外開放,一般為 private
  • 通過一個靜態方法或者列舉返回單例類物件;
  • 確保單例類的物件有且只有一個,尤其是在 多執行緒 環境下;
  • 確保單例類物件在反序列化時不會重新構建物件。

二、實現方式

2.1 懶漢模式

宣告一個靜態物件,並且在使用者第一次呼叫 getInstance 時進行初始化。

2.1.1 分析

  • synchronized 關鍵字用於在多執行緒情況下保證單例物件唯一性

  • 優點:單例只有在使用時才會被例項化,在一定程度上節約了資源

  • 缺點:

    • 每一次載入時需要及時進行例項化,響應速度稍慢
    • 每次呼叫 getInstance() 都進行同步,造成不必要的同步開銷
  • 一般不建議使用

2.1.2 原始碼

public class Singleton {
    private static Singleton instance;

    private Singleton() {

    }

    public static synchronized Singleton getInstance() {
        if (null == instance) {
            instance = new Singleton();//載入時進行例項化
        }
        return instance;
    }
}
複製程式碼

2.2 餓漢模式

宣告靜態物件時就已經初始化。

2.2.1 分析

  • 靜態物件在宣告的時候就已經初始化,從而保證了單例物件唯一性

  • 優點: 每次呼叫 getInstance() 直接取出靜態物件,不需要同步鎖,響應速度快

  • 缺點:初始化宣告物件造成了一定資源的閒置浪費

2.2.2 原始碼

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

    private Singleton() {

    }

    public static Singleton getInstance() {
        return instance;
    }
}
複製程式碼

2.3 Double Check Lock (DCL) 模式

2.3.1 分析

  • 優點:

    • 資源利用率高
    • 既能夠在需要時才初始化單例,又能夠保證執行緒安全,且單例物件初始化後呼叫 getInstance() 不進行同步鎖
  • 缺點:

    • 第一次載入時響應稍慢
    • 由於Java記憶體模型的原因偶爾會失敗
      • instance = new Singleton(); 這句程式碼並不是一個原子操作,由於 Java 編譯器允許處理器亂序執行彙編指令以及 JDK1.5 之前的 JVM (Java Memory Model, Java 記憶體模型) 中Cache、暫存器到主記憶體回寫順序的規定,該語句轉換的彙編指令無法確保順序執行
      • JDK1.5 之後,具體化了 volatile 關鍵字,因此可以直接定義成 private volatile static Singleton instance = null; ,就可以保證 instance 物件每次都是從主記憶體中讀取

2.3.2 原始碼

public class Singleton {
    private volatile static Singleton instance = null;

    private Singleton() {

    }

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

複製程式碼

2.4 靜態內部類單例模式

2.4.1 分析

強烈推薦使用

  • 優點:

    • 第一次載入 Singleton 類時並不會初始化 instance ,只有在第一次呼叫 getInstance() 時才會初始化
    • 既能保證執行緒安全,也能保證單例物件的唯一性,同時也延遲了單例的例項化

2.4.2 原始碼

public class Singleton {
    private Singleton() {
        
    }
    
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }

    /**
     * 靜態內部類
     */
    private static class SingletonHolder {
        private static final Singleton instance = new Singleton();
    }
}
複製程式碼

2.5 列舉單例

2.5.1 分析

列舉單例模式最大的優點是寫法簡單,列舉在 Java 中與普通類是一樣的,不僅能夠有欄位,還能夠有自己的方法。最重要的是預設列舉例項的建立時執行緒安全的,並且在任何情況下它都是一個單例。

在上述的幾種單例模式中,反序列化 的時候會出現重新建立物件的情況。

上述示例中如果要杜絕單利物件在被反序列化時重新生成物件,則必須加入如下方法:

private Object readResolve() throws ObjectStreamException {
        return instance;
}
複製程式碼

2.5.2 原始碼

public enum  Singleton {
    
    INSTANCE;
    
    public void doSomething() {
        // ... do something
    }
    
}
複製程式碼

2.6 使用容器實現單例模式

2.6.1 分析

在程式初始化的時候,將多種單例型別注入到一個統一的管理類中,在使用時根據 key 獲取物件對應型別的物件。

這種方式使得我們可以管理多種型別的單例,並且在使用時候可以通過統一的介面進行獲取操作,降低了使用者的使用成本,也對使用者隱藏了具體實現,降低了耦合度。

2.6.2 原始碼

public class SingletonManager {
    private static Map<String, Object> data = new HashMap<>();

    public SingletonManager() {
    }
    
    public static void register(String key, Object instance) {
        if (!data.containsKey(key)) {
            data.put(key, instance);
        }
    }
    
    public static Object get(String key) {
        return data.get(key);
    }
}
複製程式碼

三、小結

所有的單例模式核心原理都是將建構函式私有化,並且通過靜態方法獲取一個唯一的例項。

需要注意的是在獲取例項的過程中保證執行緒安全、防止反序列化導致重新生成例項物件等問題。

具體選擇哪種方式實現單例模式還需要結合專案業務邏輯。


本文對 單例模式 的分析到此就結束了,部分內容學習自 《Android原始碼設計模式 解析與實戰》

相關文章