單例模式

baiyujie02420發表於2024-03-19

單例模式在我們平時的開發中是比較常用的設計模式,那麼什麼是單例模式呢?單例模式的實現方法有那幾種方式呢?

我們首先了解下什麼是單例模式,單例模式就是確保一個類有且只有一個例項存在,而它的實現方式卻有很多種方法:餓漢式單例、懶漢式單例、DCL(double check lock)和靜態內部類單例模式,以及登記式單例和列舉單例,而這些實現方式都具有共同的特點: 1、宣告私有靜態變數; 2、私有(private)的建構函式; 3、提供靜態獲取例項的方法; 4、確保有且只有一個類的例項,並且該類物件在反序列化時不會重新構建物件。

那麼接下來我看下各種實現方式式如何實現的。

餓漢式單例
public class MyInstance {

	private final static MyInstance mInstance = new MyInstance();
	private MyInstance() {}
	public static MyInstance getInstance(){
		return mInstance;
	}

}
複製程式碼

MyInstance 物件為靜態物件,宣告時候就已經初始化好了,而且不能通過new的形式構造物件,只能通過靜態方法getInstance,直接返回建立好的例項,這種方式實現單例模式是天生具有執行緒安全的。雖然餓漢式單例,獲取的時候能夠快速返回,節省了時間,但是卻佔用了空間,例項本身為static,會一直在記憶體中帶著。

懶漢式單例
public class MyInstance {

	private static MyInstance mInstance = null;
	private MyInstance() {}
	public static synchronized MyInstance getInstance(){
		if (mInstance == null) {
			mInstance = new MyInstance();
		}
		return mInstance;
	}

}
複製程式碼

使用懶漢式單例,每次獲取例項都需要synchronized 來同步,以確保例項的唯一性,每次呼叫getInstance方法都需要同步,造成了不必要的同步開銷,並且第一次載入時,需要及時例項化,不夠快。

DCL(double check lock)
public class MyInstance {

	private static MyInstance mInstance = null;
	private MyInstance() {}
	public static MyInstance getInstance(){
		if (mInstance == null) {
			synchronized(MyInstance.class){
				if (mInstance == null) {
					mInstance = new MyInstance();
				}
			}
		}
		return mInstance;
	}

}
複製程式碼

DCL方式:需要的時候才會去初始化,同時內部使用了synchronized來保證執行緒安全。使用兩次判空操作,第一次是如果物件已經初始化了,直接返回,不需要再做同步鎖了;第二次是為了在null的情況下建立例項。 使用DCL的優點就是資源利用率高,並且效率高,但是它也不是完美的,DCL第一次載入稍微慢了點,還有就是DCL失效的問題。關於DCL失效的問題,可以看《Android原始碼設計模式解析與實戰》裡面的第二章,關於單例模式的介紹。

《Android原始碼設計模式解析與實戰》

靜態內部類單例模式
public class MyInstance {

	private MyInstance() {}
	
	public static MyInstance getInstance()
	{
		return SingleHodler.instance;
	}
	
	private static class SingleHodler{
		private static final MyInstance instance=new MyInstance();
	}

}
複製程式碼

第一次載入不會初始化instance,只要第一次呼叫getInstance方法的時候才會初始化getInstance,這種方式能夠確保執行緒安全和例項的唯一性,因為在多執行緒環境下,虛擬機器對一個類的初始化會做限制,同一時間只會允許一個執行緒去初始化一個類,這樣就從虛擬機器層面避免了大部分單例實現的問題。

登記式單例
public class MyInstance {
	
	private static Map<String,MyInstance> map = new HashMap<String,MyInstance>();

	private MyInstance() {
		System.out.print("new");
	}
	
	public static MyInstance getInstance(String key){
		if(key == null) {
			key = MyInstance.class.getName();
        }
        if(map.get(key) == null){
            try {
            	map.put(key, new MyInstance());  
            } catch (Exception e){
                e.printStackTrace();  
            }
        }
        return map.get(key);
    }
}
複製程式碼

將例項裝載加入集合map中,需要用的時候,直接從map中拿出來,一般的這種方式比較少用。

列舉單例

SdCardImpl:

public class SdCardImpl {

}
複製程式碼

列舉管理EnumManager :

public  enum EnumManager {
	SDCardManager(10)
	{	
		@Override
		public EnumManager getSingle() {
			return SDCardManager;
		}
	}
	,
	HttpManager(1) {
		@Override
		public EnumManager getSingle() {
			return null;
		}
	};
	
	public SdCardImpl getSingleton()
	{
		return new SdCardImpl();
	}
	
	
	public abstract EnumManager getSingle();
	
	private  EnumManager(int type)
	{
		
	}

}
複製程式碼

呼叫:

EnumManager.SDCardManager.getSingleton();
複製程式碼

列舉單例是從java5引入的,具有的有點: 1、程式碼簡潔 2、執行緒安全 3、列舉單例可以自己處理序列化

參考文章:《為什麼java中用列舉實現單例模式會更好 - CSDN部落格》

相關文章