七種單例模式
什么是設(shè)計模式呕乎?簡單的理解就是前人留下來的一些經(jīng)驗總結(jié)而已昔汉,然后把這些經(jīng)驗起了個名字叫Design Pattern让网,翻譯過來就是設(shè)計模式蛤签,通過使用設(shè)計模式可以讓我們的代碼復(fù)用性更高,可維護性更高栅哀,讓你的代碼寫的更優(yōu)雅震肮。設(shè)計模式理論上有23種,今天就先來學(xué)習(xí)下最常用的單例模式留拾。
單例模式
保證一個類僅有一個實例戳晌,并提供一個訪問它的全局訪問點。
寫單例模式有7中方法痴柔,各有利弊沦偎,下面逐個分析
1、餓漢式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){
}
public static Singleton getInstance() {
return instance;
}
}
這種方式創(chuàng)建的單例和名字很貼切咳蔚,饑不擇食豪嚎,在JVM加載類的時候就創(chuàng)建了實例對象,不管你用還是不用谈火,先創(chuàng)建了再說侈询。
如果一直沒有使用,便浪費了空間糯耍,典型的空間換時間的做法扔字,每次調(diào)用的時候,就不需要再判斷温技,節(jié)省了運行時間革为。
餓漢式單例實現(xiàn)比較簡單,單例對象在初始化的時候就創(chuàng)建了舵鳞,適合單例對象占用內(nèi)存比較小的情況震檩。
但是,如果單例初始化操作比較耗時系任,或者單例占用內(nèi)存比較大恳蹲,或者單例在某種特定情況才會使用虐块,一般情況不會使用,這個時候使用餓漢式單例模式就不適合嘉蕾。需要使用懶漢式單例按需延時加載贺奠。
2、餓漢式(非線程安全)
public class SingletonLazyNotSafe {
private static SingletonLazyNotSafe instance = null;
private SingletonLazyNotSafe() {
}
public static SingletonLazyNotSafe getInstance() {
if (instance == null) {
instance = new SingletonLazyNotSafe();
}
return instance;
}
}
這種方式創(chuàng)建的單例對象错忱,因為聲明了一個靜態(tài)對象該值為空儡率,在JVM加載類的時候,并不會創(chuàng)建實例對象以清,節(jié)省了資源儿普,只有在第一次調(diào)用getInstance()
才會初始化對象。
但是這種模式的不能工作在多線程中掷倔,在多線程調(diào)用getInstance()
可能會產(chǎn)生多個實例對象眉孩。
3、懶漢式(線程安全)
public class SingletonSafe {
private static SingletonSafe instance = null;
private SingletonSafe() {
}
public static synchronized SingletonSafe getInstance() {
if (instance == null) {
instance = new SingletonSafe();
}
return instance;
}
}
這種單例模式在getInstance()
的方法前面加上了synchronized
關(guān)鍵字勒葱,用來保證多線程訪問情況下加上同步鎖浪汪,將其它線程阻塞在同步鎖外面,這樣保證獲取到是單個實例對象凛虽。
4死遭、雙重校驗鎖(DCL)
上面的單例模式在多線程調(diào)用getInstance()
會產(chǎn)生性能問題。于是有了DCL單例模式凯旋。
DCL 是Double Check Lock的縮寫呀潭。雙重校驗鎖的意思是,只要保證在 檢測null的操作和創(chuàng)建對象的操作時候至非,這兩個操作能夠原子地進行钠署,那么單例就已經(jīng)保證了。
public class SingletonDCL {
private static volatile SingletonDCL instance;
private SingletonDCL() {
}
public static SingletonDCL getInstance() {
if (instance == null) {
synchronized (SingletonDCL.class) {
if (instance == null) {
instance = new SingletonDCL();
}
}
}
return instance;
}
}
這種寫法在getSingleton()方法中對singleton進行了兩次判空荒椭,第一次是為了不必要的同步踏幻,第二次是在singleton等于null的情況下才創(chuàng)建實例。在這里用到了volatile關(guān)鍵字戳杀。
這種寫法该面,在JDK5之前是不安全的:
- java的new不是原子的,具體在更細的層面還是有諸多步驟:
- 分配新對象的內(nèi)存
- 調(diào)用類的構(gòu)造器信卡,初始化成員字段
- instance被賦為指向新的對象的引用隔缀。
說白了,JDK5之前不能保證有volatile修飾的對象構(gòu)造內(nèi)部是有序的傍菇。
- 線程A發(fā)現(xiàn)instance沒有被實例化猾瘸,它獲得鎖,然后去實例化該對象,JVM容許在沒有完全實例化完成的時候牵触,將實例的指針賦給這個instance變量淮悼,而此時instance==null就為false了,在初始化完成之前揽思,線程B進入此方法袜腥,發(fā)現(xiàn)instance不為空,認為已經(jīng)初始化完成了钉汗,于是便使用了這個尚未完全初始化的實例對象羹令,可能引起其他的異常。
雙重校驗鎖:既可以達到線程安全损痰,也可以使性能不受很大的影響福侈,換句話說在保證線程安全的前提下,既節(jié)省空間也節(jié)省了時間卢未,集合了「餓漢式」和兩種「懶漢式」的優(yōu)點肪凛,取其精華,去其槽粕辽社。
5显拜、靜態(tài)內(nèi)部類
采用內(nèi)部類,在這個類里面去創(chuàng)建實例對象爹袁,只要程序中不使用內(nèi)部類JVM就不會去加載這個單例類,也就不會創(chuàng)建單例對象矮固。從而實現(xiàn)「懶漢式」的延遲加載和線程安全失息。
public class SingletonInner {
private SingletonInner() {
}
public static SingletonInner getInstance() {
return SingletonInnerHolder.instance;
}
private static class SingletonInnerHolder{
private static final SingletonInner instance = new SingletonInner();
}
}
第一次加載Singleton類時并不會初始化sInstance,只有第一次調(diào)用getInstance方法時虛擬機加載SingletonHolder 并初始化sInstance 档址,這樣不僅能確保線程安全也能保證Singleton類的唯一性盹兢,所以推薦使用靜態(tài)內(nèi)部類單例模式。
然而這還不是最簡單的方式守伸,《Effective Java》中作者推薦了一種更簡潔方便的使用方式绎秒,就是使用「枚舉」。
6尼摹、枚舉
用枚舉來實現(xiàn)單實例控制會更加簡潔见芹,而且無償?shù)靥峁┝诵蛄谢瘷C制,并由JVM從根本上提供保障蠢涝,絕對防止多次實例化玄呛,是更簡潔、高效和二、安全的實現(xiàn)單例的方式徘铝。
public enum SingletonEnum {
INSTANCE;
}
7、使用容器
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String,Object>();
private Singleton() {
}
public static void registerService(String key, Objectinstance) {
if (!objMap.containsKey(key) ) {
objMap.put(key, instance) ;
}
}
public static ObjectgetService(String key) {
return objMap.get(key) ;
}
}
這種事用SingletonManager 將多種單例類統(tǒng)一管理,在使用時根據(jù)key獲取對象對應(yīng)類型的對象惕它。這種方式使得我們可以管理多種類型的單例怕午,并且在使用時可以通過統(tǒng)一的接口進行獲取操作,降低了用戶的使用成本淹魄,也對用戶隱藏了具體實現(xiàn)郁惜,降低了耦合度。