單例模式(Singleton Pattern)

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

某些對象我們只需要一個,比如線程池、緩存、註冊表等等。如果這些類擁有多個實例,可能會產生很多問題。

使用單例模式可以確保我們使用的這些全局資源只有一份。

一個經典的單例模式的實現:

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

由於Singleton類沒有公共的構造方法,我們並不能直接創建這個類的實例,而是隻能通過調用靜態的getInstance()方法來獲取對單例對象的一個引用。當調用getInstance()時,如果該類還沒有任何實例則創建一個實例並返回對它的引用,如果已存在一個實例,則直接返回該實例的一個引用。

這樣就確保了單例的類最多只能有一個實例。


多線程下的隱患
在多線程的情況下,如果兩個線程幾乎同時調用getInstance()方法會發生什麼呢?有可能會創建出兩個該類的實例。

我們可以將getInstance()方法變爲同步方法來解決這個問題:

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

性能問題

然而,事實上,我們只有在uniqueInstance爲null的時候才需要進行同步,當這個類已經有實例之後就不存在多線程隱患了。

因此我們將getInstance()方法變爲同步方法有可能很大程度的拖垮性能。

如果將getInstance()方法變爲同步方法真的影響到了性能,我們可以選擇在靜態初始化時創建這個單例。

public class Singleton{
	private static Singleton uniqueInstance=new Singleton();
	private Singleton(){}
	public static Singleton getInstance(){
	return uniqueInstance;
	}
}
這樣自然也能確保單例。

問題是,前面的例子中都是在需要一個實例的時候在創建單例,這個例子中在類初始化時就創建了單例。如果這個對象非常耗資源,而程序中又一直沒有用到它,這樣便是在浪費資源了。


“雙重檢查加鎖”

public class Singleton{
	private volatile static Singleton uniqueInstance;//volatile修飾被不同線程訪問和修改的變量
	private Singleton(){}
	public static Singleton getInstance(){
	if(uniqueInstance==null){
		synchronized(Singleton.class){//對整個類加鎖
			if(uniqueInstance==null){
				uniqueInstance=new Singleton();
			}
		}	
	}
	return uniqueInstance;
	}
}
這樣就可以在節省資源的同時確保正確性和高效性。只是實現方法有點不夠簡潔了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章