Java 单例模式探讨
以下是我再次研究單例(Java?單例模式缺點(diǎn))時(shí)在網(wǎng)上收集的資料,相信你們看完就對(duì)單例完全掌握了
Java單例模式應(yīng)該是看起來(lái)以及用起來(lái)簡(jiǎn)單的一種設(shè)計(jì)模式,但是就實(shí)現(xiàn)方式以及原理來(lái)說(shuō),也并不淺顯哦。
?
總結(jié)一下我所知道的單例模式實(shí)現(xiàn)方式:
?
1.預(yù)先加載法
?
Java代碼??
優(yōu)點(diǎn):??
1.線程安全的
2.在類加載的同時(shí)已經(jīng)創(chuàng)建好一個(gè)靜態(tài)對(duì)象,調(diào)用時(shí)反應(yīng)速度快。
缺點(diǎn):?資源利用效率不高,可能getInstance永遠(yuǎn)不會(huì)執(zhí)行到,但是執(zhí)行了該類的其他靜態(tài)方法或者加載了該類(class.forName),那么這個(gè)實(shí)例仍然初始化了
?
2.initialization on demand,延遲加載法? (考慮多線程)
?
Java代碼??
優(yōu)點(diǎn):?資源利用率高,不執(zhí)行g(shù)etInstance就不會(huì)被實(shí)例,可以執(zhí)行該類其他靜態(tài)方法。
缺點(diǎn):?第一次加載時(shí)發(fā)應(yīng)不快? ,多線程使用不必要的同步開銷大
?
3.initialization on demand double check 雙重檢測(cè)(?考慮多線程?)
?
Java代碼??
優(yōu)點(diǎn):?資源利用率高,?不執(zhí)行g(shù)etInstance就不會(huì)被實(shí)例,可以執(zhí)行該類其他靜態(tài)方法。
缺點(diǎn):?第一次加載時(shí)發(fā)應(yīng)不快? ,由于java 內(nèi)存模型一些原因偶爾會(huì)失敗
?
4.initialization on demand holder? (考慮多線程)
?
Java代碼??
優(yōu)點(diǎn):?資源利用率高,?不執(zhí)行g(shù)etInstance就不會(huì)被實(shí)例,可以執(zhí)行該類其他靜態(tài)方法。
缺點(diǎn):?第一次加載時(shí)發(fā)應(yīng)不快
?
?
?
總結(jié):?一般采用 1 即可,若對(duì)資源十分在意也可考慮 4 ,不要使用2,3了。
?
?
測(cè)試代碼:(暫不探討Class.forName類加載機(jī)制)
?
Java代碼作為對(duì)象的創(chuàng)建模式[GOF95], 單例模式確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。這個(gè)類稱為單例類。 注:本文乃閻宏博士的《Java與模式》一書的第十五章。 引言 單例模式的要點(diǎn) 單例單例 顯然單例模式的要點(diǎn)有三個(gè);一是某各類只能有一個(gè)實(shí)例;二是它必須自行創(chuàng)建這個(gè)事例;三是它必須自行向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。在下面的對(duì)象圖中,有一個(gè)"單例對(duì)象",而"客戶甲"、"客戶乙" 和"客戶丙"是單例對(duì)象的三個(gè)客戶對(duì)象。可以看到,所有的客戶對(duì)象共享一個(gè)單例對(duì)象。而且從單例對(duì)象到自身的連接線可以看出,單例對(duì)象持有對(duì)自己的引用。 資源管理 一些資源管理器常常設(shè)計(jì)成單例模式。 在計(jì)算機(jī)系統(tǒng)中,需要管理的資源包括軟件外部資源,譬如每臺(tái)計(jì)算機(jī)可以有若干個(gè)打印機(jī),但只能有一個(gè)Printer Spooler, 以避免兩個(gè)打印作業(yè)同時(shí)輸出到打印機(jī)中。每臺(tái)計(jì)算機(jī)可以有若干傳真卡,但是只應(yīng)該有一個(gè)軟件負(fù)責(zé)管理傳真卡,以避免出現(xiàn)兩份傳真作業(yè)同時(shí)傳到傳真卡中的情況。每臺(tái)計(jì)算機(jī)可以有若干通信端口,系統(tǒng)應(yīng)當(dāng)集中管理這些通信端口,以避免一個(gè)通信端口同時(shí)被兩個(gè)請(qǐng)求同時(shí)調(diào)用。 需要管理的資源包括軟件內(nèi)部資源,譬如,大多數(shù)的軟件都有一個(gè)(甚至多個(gè))屬性(properties)文件存放系統(tǒng)配置。這樣的系統(tǒng)應(yīng)當(dāng)由一個(gè)對(duì)象來(lái)管理一個(gè)屬性文件。 需要管理的軟件內(nèi)部資源也包括譬如負(fù)責(zé)記錄網(wǎng)站來(lái)訪人數(shù)的部件,記錄軟件系統(tǒng)內(nèi)部事件、出錯(cuò)信息的部件,或是對(duì)系統(tǒng)的表現(xiàn)進(jìn)行檢查的部件等。這些部件都必須集中管理,不可政出多頭。 這些資源管理器構(gòu)件必須只有一個(gè)實(shí)例,這是其一;它們必須自行初始化,這是其二;允許整個(gè)系統(tǒng)訪問(wèn)自己這是其三。因此,它們都滿足單例模式的條件,是單例模式的應(yīng)用。 一個(gè)例子:Windows 回收站 Windows?9x 以后的視窗系統(tǒng)中都有一個(gè)回收站,下圖就顯示了Windows 2000 的回收站。 在整個(gè)視窗系統(tǒng)中,回收站只能有一個(gè)實(shí)例,整個(gè)系統(tǒng)都使用這個(gè)惟一的實(shí)例,而且回收站自行提供自己的實(shí)例。因此,回收站是單例模式的應(yīng)用。 雙重檢查成例 在本章最后的附錄里研究了雙重檢查成例。雙重檢查成例與單例模式并無(wú)直接的關(guān)系,但是由于很多C 語(yǔ)言設(shè)計(jì)師在單例模式里面使用雙重檢查成例,所以這一做法也被很多Java 設(shè)計(jì)師所模仿。因此,本書在附錄里提醒讀者,雙重檢查成例在Java 語(yǔ)言里并不能成立,詳情請(qǐng)見本章的附錄。 單例模式的結(jié)構(gòu) 單例模式有以下的特點(diǎn): .. 單例類只可有一個(gè)實(shí)例。 .. 單例類必須自己創(chuàng)建自己這惟一的實(shí)例。 .. 單例類必須給所有其他對(duì)象提供這一實(shí)例。 雖然單例模式中的單例類被限定只能有一個(gè)實(shí)例,但是單例模式和單例類可以很容易被推廣到任意且有限多個(gè)實(shí)例的情況,這時(shí)候稱它為多例模式(Multiton Pattern) 和多例類(Multiton Class),請(qǐng)見"專題:多例(Multiton )模式與多語(yǔ)言支持"一章。單例類的簡(jiǎn)略類圖如下所示。 由于Java 語(yǔ)言的特點(diǎn),使得單例模式在Java 語(yǔ)言的實(shí)現(xiàn)上有自己的特點(diǎn)。這些特點(diǎn)主要表現(xiàn)在單例類如何將自己實(shí)例化上。 餓漢式單例類餓漢式單例類是在Java 語(yǔ)言里實(shí)現(xiàn)得最為簡(jiǎn)便的單例類,下面所示的類圖描述了一個(gè)餓漢式單例類的典型實(shí)現(xiàn)。 從圖中可以看出,此類已經(jīng)自已將自己實(shí)例化。 代碼清單1:餓漢式單例類 public class EagerSingleton? {? private static final EagerSingleton m_instance =? new EagerSingleton();? /**? * 私有的默認(rèn)構(gòu)造子? */? private EagerSingleton() { }? /**? * 靜態(tài)工廠方法? */? public static EagerSingleton getInstance()? { ·224·Java 與模式? return m_instance;? } }? 讀者可以看出,在這個(gè)類被加載時(shí),靜態(tài)變量m_instance 會(huì)被初始化,此時(shí)類的私有構(gòu)造子會(huì)被調(diào)用。這時(shí)候,單例類的惟一實(shí)例就被創(chuàng)建出來(lái)了。 Java 語(yǔ)言中單例類的一個(gè)最重要的特點(diǎn)是類的構(gòu)造子是私有的,從而避免外界利用構(gòu)造子直接創(chuàng)建出任意多的實(shí)例。值得指出的是,由于構(gòu)造子是私有的,因此,此類不能被繼承。 懶漢式單例類 與餓漢式單例類相同之處是,類的構(gòu)造子是私有的。與餓漢式單例類不同的是,懶漢式單例類在第一次被引用時(shí)將自己實(shí)例化。如果加載器是靜態(tài)的,那么在懶漢式單例類被加載時(shí)不會(huì)將自己實(shí)例化。如下圖所示,類圖中給出了一個(gè)典型的餓漢式單例類實(shí)現(xiàn)。 代碼清單2:懶漢式單例類 package com.javapatterns.singleton.demos; public class LazySingleton { private static LazySingleton m_instance = null; /** * 私有的默認(rèn)構(gòu)造子,保證外界無(wú)法直接實(shí)例化 */ private LazySingleton() { } /** * 靜態(tài)工廠方法,返還此類的惟一實(shí)例 */ synchronized public static LazySingleton getInstance() { if (m_instance == null) { m_instance = new LazySingleton(); } return m_instance; } }? 讀者可能會(huì)注意到,在上面給出懶漢式單例類實(shí)現(xiàn)里對(duì)靜態(tài)工廠方法使用了同步化,以處理多線程環(huán)境。有些設(shè)計(jì)師在這里建議使用所謂的"雙重檢查成例"。必須指出的是,"雙重檢查成例"不可以在Java 語(yǔ)言中使用。不十分熟悉的讀者,可以看看后面給出的小節(jié)。 同樣,由于構(gòu)造子是私有的,因此,此類不能被繼承。餓漢式單例類在自己被加載時(shí)就將自己實(shí)例化。即便加載器是靜態(tài)的,在餓漢式單例類被加載時(shí)仍會(huì)將自己實(shí)例化。單從資源利用效率角度來(lái)講,這個(gè)比懶漢式單例類稍差些。 從速度和反應(yīng)時(shí)間角度來(lái)講,則比懶漢式單例類稍好些。然而,懶漢式單例類在實(shí)例化時(shí), 必須處理好在多個(gè)線程同時(shí)首次引用此類時(shí)的訪問(wèn)限制問(wèn)題,特別是當(dāng)單例類作為資源控制器,在實(shí)例化時(shí)必然涉及資源初始化,而資源初始化很有可能耗費(fèi)時(shí)間。這意味著出現(xiàn)多線程同時(shí)首次引用此類的機(jī)率變得較大。 餓漢式單例類可以在Java 語(yǔ)言內(nèi)實(shí)現(xiàn), 但不易在C++ 內(nèi)實(shí)現(xiàn),因?yàn)殪o態(tài)初始化在C++ 里沒(méi)有固定的順序,因而靜態(tài)的m_instance 變量的初始化與類的加載順序沒(méi)有保證,可能會(huì)出問(wèn)題。這就是為什么GoF 在提出單例類的概念時(shí),舉的例子是懶漢式的。他們的書影響之大,以致Java 語(yǔ)言中單例類的例子也大多是懶漢式的。實(shí)際上,本書認(rèn)為餓漢式單例類更符合Java 語(yǔ)言本身的特點(diǎn)。 登記式單例類 登記式單例類是GoF 為了克服餓漢式單例類及懶漢式單例類均不可繼承的缺點(diǎn)而設(shè)計(jì)的。本書把他們的例子翻譯為Java 語(yǔ)言,并將它自己實(shí)例化的方式從懶漢式改為餓漢式。只是它的子類實(shí)例化的方式只能是懶漢式的, 這是無(wú)法改變的。如下圖所示是登記式單例類的一個(gè)例子,圖中的關(guān)系線表明,此類已將自己實(shí)例化。 代碼清單3:登記式單例類 import java.util.HashMap; public class RegSingleton { static private HashMap m_registry = new HashMap(); static { RegSingleton x = new RegSingleton(); m_registry.put( x.getClass().getName() , x); } /** * 保護(hù)的默認(rèn)構(gòu)造子 */ protected RegSingleton() {} /** * 靜態(tài)工廠方法,返還此類惟一的實(shí)例 */ static public RegSingleton getInstance(String name) { if (name == null) { name = "com.javapatterns.singleton.demos.RegSingleton"; } if (m_registry.get(name) == null) { try { m_registry.put( name, Class.forName(name).newInstance() ) ; } catch(Exception e) { System.out.println("Error happened."); } } return (RegSingleton) (m_registry.get(name) ); } /** * 一個(gè)示意性的商業(yè)方法 */ public String about() { return "Hello, I am RegSingleton."; } } 它的子類RegSingletonChild 需要父類的幫助才能實(shí)例化。下圖所示是登記式單例類子類的一個(gè)例子。圖中的關(guān)系表明,此類是由父類將子類實(shí)例化的。 下面是子類的源代碼。 代碼清單4:登記式單例類的子類 import java.util.HashMap; public class RegSingletonChild extends RegSingleton { public RegSingletonChild() {} /** * 靜態(tài)工廠方法 */ static public RegSingletonChild getInstance() { return (RegSingletonChild) RegSingleton.getInstance( "com.javapatterns.singleton.demos.RegSingletonChild" ); } /** * 一個(gè)示意性的商業(yè)方法 */ public String about() { return "Hello, I am RegSingletonChild."; } }?? 在GoF 原始的例子中,并沒(méi)有g(shù)etInstance() 方法,這樣得到子類必須調(diào)用的getInstance(String name)方法并傳入子類的名字,因此很不方便。本章在登記式單例類子類的例子里,加入了getInstance() 方法,這樣做的好處是RegSingletonChild 可以通過(guò)這個(gè)方法,返還自已的實(shí)例。而這樣做的缺點(diǎn)是,由于數(shù)據(jù)類型不同,無(wú)法在RegSingleton 提供這樣一個(gè)方法。由于子類必須允許父類以構(gòu)造子調(diào)用產(chǎn)生實(shí)例,因此,它的構(gòu)造子必須是公開的。這樣一來(lái),就等于允許了以這樣方式產(chǎn)生實(shí)例而不在父類的登記中。這是登記式單例類的一個(gè)缺點(diǎn)。 GoF 曾指出,由于父類的實(shí)例必須存在才可能有子類的實(shí)例,這在有些情況下是一個(gè)浪費(fèi)。這是登記式單例類的另一個(gè)缺點(diǎn)。 |
GoF 曾指出,由于父類的實(shí)例必須存在才可能有子類的實(shí)例,這在有些情況下是一個(gè)浪費(fèi)。這是登記式單例類的另一個(gè)缺點(diǎn)。
在什么情況下使用單例模式
使用單例模式的條件
使用單例模式有一個(gè)很重要的必要條件:
在一個(gè)系統(tǒng)要求一個(gè)類只有一個(gè)實(shí)例時(shí)才應(yīng)當(dāng)使用單例模式。反過(guò)來(lái)說(shuō),如果一個(gè)類可以有幾個(gè)實(shí)例共存,那么就沒(méi)有必要使用單例類。但是有經(jīng)驗(yàn)的讀者可能會(huì)看到很多不當(dāng)?shù)厥褂脝卫J降睦?#xff0c;可見做到上面這一點(diǎn)并不容易,下面就是一些這樣的情況。
例子一
問(wèn):我的一個(gè)系統(tǒng)需要一些"全程"變量。學(xué)習(xí)了單例模式后,我發(fā)現(xiàn)可以使用一個(gè)單例類盛放所有的"全程"變量。請(qǐng)問(wèn)這樣做對(duì)嗎?
答:這樣做是違背單例模式的用意的。單例模式只應(yīng)當(dāng)在有真正的"單一實(shí)例"的需求時(shí)才可使用。
一個(gè)設(shè)計(jì)得當(dāng)?shù)南到y(tǒng)不應(yīng)當(dāng)有所謂的"全程"變量,這些變量應(yīng)當(dāng)放到它們所描述的實(shí)體所對(duì)應(yīng)的類中去。將這些變量從它們所描述的實(shí)體類中抽出來(lái), 放到一個(gè)不相干的單例類中去,會(huì)使得這些變量產(chǎn)生錯(cuò)誤的依賴關(guān)系和耦合關(guān)系。
例子二
問(wèn):我的一個(gè)系統(tǒng)需要管理與數(shù)據(jù)庫(kù)的連接。學(xué)習(xí)了單例模式后,我發(fā)現(xiàn)可以使用一個(gè)單例類包裝一個(gè)Connection 對(duì)象,并在finalize()方法中關(guān)閉這個(gè)Connection 對(duì)象。這樣的話,在這個(gè)單例類的實(shí)例沒(méi)有被人引用時(shí),這個(gè)finalize()對(duì)象就會(huì)被調(diào)用,因此,Connection 對(duì)象就會(huì)被釋放。這多妙啊。
答:這樣做是不恰當(dāng)?shù)摹3怯袉我粚?shí)例的需求,不然不要使用單例模式。在這里Connection 對(duì)象可以同時(shí)有幾個(gè)實(shí)例共存,不需要是單一實(shí)例。
單例模式有很多的錯(cuò)誤使用案例都與此例子相似,它們都是試圖使用單例模式管理共享資源的生命周期,這是不恰當(dāng)?shù)摹?/span>
單例類的狀態(tài)
有狀態(tài)的單例類
一個(gè)單例類可以是有狀態(tài)的(stateful),一個(gè)有狀態(tài)的單例對(duì)象一般也是可變(mutable) 單例對(duì)象。
有狀態(tài)的可變的單例對(duì)象常常當(dāng)做狀態(tài)庫(kù)(repositary)使用。比如一個(gè)單例對(duì)象可以持有一個(gè)int 類型的屬性,用來(lái)給一個(gè)系統(tǒng)提供一個(gè)數(shù)值惟一的序列號(hào)碼,作為某個(gè)販賣系統(tǒng)的賬單號(hào)碼。當(dāng)然,一個(gè)單例類可以持有一個(gè)聚集,從而允許存儲(chǔ)多個(gè)狀態(tài)。
沒(méi)有狀態(tài)的單例類
另一方面,單例類也可以是沒(méi)有狀態(tài)的(stateless),僅用做提供工具性函數(shù)的對(duì)象。既然是為了提供工具性函數(shù),也就沒(méi)有必要?jiǎng)?chuàng)建多個(gè)實(shí)例,因此使用單例模式很合適。一個(gè)沒(méi)有狀態(tài)的單例類也就是不變(Immutable) 單例類; 關(guān)于不變模式,讀者可以參見本書的"不變(Immutable )模式"一章。
多個(gè)JVM 系統(tǒng)的分散式系統(tǒng)
EJB 容器有能力將一個(gè)EJB 的實(shí)例跨過(guò)幾個(gè)JVM 調(diào)用。由于單例對(duì)象不是EJB,因此,單例類局限于某一個(gè)JVM 中。換言之,如果EJB 在跨過(guò)JVM 后仍然需要引用同一個(gè)單例類的話,這個(gè)單例類就會(huì)在數(shù)個(gè)JVM 中被實(shí)例化,造成多個(gè)單例對(duì)象的實(shí)例出現(xiàn)。一個(gè)J2EE應(yīng)用系統(tǒng)可能分布在數(shù)個(gè)JVM 中,這時(shí)候不一定需要EJB 就能造成多個(gè)單例類的實(shí)例出現(xiàn)在不同JVM 中的情況。
如果這個(gè)單例類是沒(méi)有狀態(tài)的,那么就沒(méi)有問(wèn)題。因?yàn)闆](méi)有狀態(tài)的對(duì)象是沒(méi)有區(qū)別的。但是如果這個(gè)單例類是有狀態(tài)的,那么問(wèn)題就來(lái)了。舉例來(lái)說(shuō),如果一個(gè)單例對(duì)象可以持有一個(gè)int 類型的屬性,用來(lái)給一個(gè)系統(tǒng)提供一個(gè)數(shù)值惟一的序列號(hào)碼,作為某個(gè)販賣系統(tǒng)的賬單號(hào)碼的話,用戶會(huì)看到同一個(gè)號(hào)碼出現(xiàn)好幾次。
在任何使用了EJB、RMI 和JINI 技術(shù)的分散式系統(tǒng)中,應(yīng)當(dāng)避免使用有狀態(tài)的單例模式。
多個(gè)類加載器
同一個(gè)JVM 中會(huì)有多個(gè)類加載器,當(dāng)兩個(gè)類加載器同時(shí)加載同一個(gè)類時(shí),會(huì)出現(xiàn)兩個(gè)實(shí)例。在很多J2EE 服務(wù)器允許同一個(gè)服務(wù)器內(nèi)有幾個(gè)Servlet 引擎時(shí),每一個(gè)引擎都有獨(dú)立的類加載器,經(jīng)有不同的類加載器加載的對(duì)象之間是絕緣的。
比如一個(gè)J2EE 系統(tǒng)所在的J2EE 服務(wù)器中有兩個(gè)Servlet 引擎:一個(gè)作為內(nèi)網(wǎng)給公司的網(wǎng)站管理人員使用;另一個(gè)給公司的外部客戶使用。兩者共享同一個(gè)數(shù)據(jù)庫(kù),兩個(gè)系統(tǒng)都需要調(diào)用同一個(gè)單例類。如果這個(gè)單例類是有狀態(tài)的單例類的話,那么內(nèi)網(wǎng)和外網(wǎng)用戶看到的單例對(duì)象的狀態(tài)就會(huì)不同。除非系統(tǒng)有協(xié)調(diào)機(jī)制,不然在這種情況下應(yīng)當(dāng)盡量避免使用有狀態(tài)的單例類。
from:?http://blog.csdn.net/it_man/article/details/5787567
總結(jié)
以上是生活随笔為你收集整理的Java 单例模式探讨的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java常见异常说明汇总
- 下一篇: JAVA访问控制符(写给初学者的)