設計模式--單例(Singleton Pattern)

愛穿襯衫的程式設計師發表於2018-12-17

定義

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

使用場景

避免產生多個物件消耗過多的資源,或者某種型別的物件只有一個。

1、餓漢模式

public static class Singleton {
    private static final instance Singleton = new Singleton();
    
    private Singleton() { // 私有建構函式
    }
    
    public static Singleton getInstance() {
        return instance;
    }
}
複製程式碼

instance是靜態成員變數,在宣告時就被例項化。

2、懶漢模式

public static class Singleton {
    private static final instance Singleton = null;
    
    private Singleton() { // 私有建構函式
    }
    
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton()
        }
        return instance;
    }
}
複製程式碼

instance只是在初次呼叫時初始化,synchronized,每次呼叫getInstance()方法都會進行同步,這樣會消耗不必要的資源,這也是懶漢模式最大的問題。

3、Double Check Lock(DCL)

public static class Singleton {
    private static final instance Singleton = null;
    
    private Singleton() { // 私有建構函式
    }
    
    public static Singleton getInstance() {
        if(instance == null) {
            synchronized(Singleton.class) {
                if(instance == null) {
                    instance = new Singleton()
                }
            }
        }
        return instance;
    }
}
複製程式碼

DCL有兩次判空,第一次判空,是為了避免不必要的同步;第二次判空,是為了初次初始化時,只初始化一次。

instance = new Singletone(); 這行程式碼最終會編譯成多條彙編指令,它大致做了三件事:

  1. 給Singleton的例項分配記憶體;
  2. 呼叫Singleton()的建構函式,初始化成員欄位;
  3. 將instance物件指向分配的記憶體空間(此時instance不再是null了);

DCL失效情況:
由於Java編譯器允許處理器亂序執行,以及JDK1.5之前JMM(Java Memory Model)中Cache、暫存器到記憶體回寫順序的規定,上面第二和第三的順序是無法保證的。也就是說有可能JVM會為新的Singleton例項分配空間,然後直接賦值給instance成員,然後再去初始化這個Singleton例項,這樣就可能出錯了。我們以A、B兩個執行緒為例:

  1. A、B執行緒同時進入了第一個if判斷
  2. A首先進入synchronized塊,由於instance為null,所以它執行instance = new Singleton();
  3. 由於JVM內部的優化機制,JVM先畫出了一些分配給Singleton例項的空白記憶體,並賦值給instance成員(注意此時JVM沒有開始初始化這個例項),然後A離開了synchronized塊。
  4. B進入synchronized塊,由於instance此時不是null,因此它馬上離開了synchronized塊並將結果返回給呼叫該方法的程式。
  5. 此時B執行緒打算使用Singleton例項,卻發現它沒有被初始化,於是錯誤發生了。

在JDK1.5之後,SUN公司調整了JVM,增加了volatile關鍵字,只需將instance的定義改成private volatile static Singleton instance = null就可以儲存instance物件每次都是從主記憶體總讀取。

4.靜態內部類單例模式

public static class Singleton {
    private Singleton() { // 私有建構函式
    }
    
    private static class SingletonHolder {
        private static final instance Singleton = new Singleton();
    }
    
    public static synchronized Singleton getInstance() {
        return SingletonHolder.instance;
    }
}
複製程式碼

當第一次載入Singleton類時,並不會初始化,只有當第一次呼叫getInstance()方法,會導致虛擬機器載入SingletonHolder類,此時才是初始化。利用的Java類載入機制

不同的類裝載器,可能會導致產生多個單例物件:

public static Class getClass(String className) throws ClassNotFoundException {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    if (cl == null) {
        cl = Singleton.class.getClassLoader();
    }
    return cl.loadClass(className);
}
複製程式碼

如果Singleton實現了Serializable介面,反序列化時可能產生多個例項物件:

private Object readResolve() { // 重寫readResolve方法
    return instance;
}
複製程式碼

相關文章