單例模式是最簡(jiǎn)單的設(shè)計(jì)模式之一,但是對(duì)于Java的開發(fā)者來(lái)說(shuō),它卻有很多缺陷。在本月的專欄中,David Geary探討了單例模式以及在面對(duì)多線程(multithreading)、類裝載器(classloaders)和序列化(serialization)時(shí)如何處理這些缺陷。
單例模式適合于一個(gè)類只有一個(gè)實(shí)例的情況,比如窗口管理器,打印緩沖池和文件系統(tǒng),它們都是原型的例子。典型的情況是,那些對(duì)象的類型被遍及一個(gè)軟件系統(tǒng)的不同對(duì)象訪問(wèn),因此需要一個(gè)全局的訪問(wèn)指針,這便是眾所周知的單例模式的應(yīng)用。當(dāng)然這只有在你確信你不再需要任何多于一個(gè)的實(shí)例的情況下。
單例模式的用意在于前一段中所關(guān)心的。通過(guò)單例模式你可以:
盡管單例設(shè)計(jì)模式如在下面的圖中的所顯示的一樣是最簡(jiǎn)單的設(shè)計(jì)模式,但對(duì)于粗心的Java開發(fā)者來(lái)說(shuō)卻呈現(xiàn)出許多缺陷。這篇文章討論了單例模式并揭示了那些缺陷。
注意:你可以從Resources下載這篇文章的源代碼。
單例模式
在《設(shè)計(jì)模式》一書中,作者這樣來(lái)敘述單例模式的:確保一個(gè)類只有一個(gè)實(shí)例并提供一個(gè)對(duì)它的全局訪問(wèn)指針。
下圖說(shuō)明了單例模式的類圖。
(圖1)

單例模式的類圖
正如你在上圖中所看到的,這不是單例模式的完整部分。此圖中單例類保持了一個(gè)對(duì)唯一的單例實(shí)例的靜態(tài)引用,并且會(huì)從靜態(tài)getInstance()方法中返回對(duì)那個(gè)實(shí)例的引用。
例1顯示了一個(gè)經(jīng)典的單例模式的實(shí)現(xiàn)。
例1.經(jīng)典的單例模式
- public class ClassicSingleton {
- private static ClassicSingleton instance = null;
- protected ClassicSingleton() {
- // Exists only to defeat instantiation.
- }
- public static ClassicSingleton getInstance() {
- if(instance == null) {
- instance = new ClassicSingleton();
- }
- return instance;
- }
- }
在例1中的單例模式的實(shí)現(xiàn)很容易理解。ClassicSingleton類保持了一個(gè)對(duì)單獨(dú)的單例實(shí)例的靜態(tài)引用,并且從靜態(tài)方法getInstance()中返回那個(gè)引用。
關(guān)于ClassicSingleton類,有幾個(gè)讓我們感興趣的地方。首先,ClassicSingleton使用了一個(gè)眾所周知的懶漢式實(shí)例化去創(chuàng)建那個(gè)單例類的引用;結(jié)果,這個(gè)單例類的實(shí)例直到getInstance()方法被第一次調(diào)用時(shí)才被創(chuàng)建。這種技巧可以確保單例類的實(shí)例只有在需要時(shí)才被建立出來(lái)。其次,注意ClassicSingleton實(shí)現(xiàn)了一個(gè)protected的構(gòu)造方法,這樣客戶端不能直接實(shí)例化一個(gè)ClassicSingleton類的實(shí)例。然而,你會(huì)驚奇的發(fā)現(xiàn)下面的代碼完全合法:
- public class SingletonInstantiator {
- public SingletonInstantiator() {
- ClassicSingleton instance = ClassicSingleton.getInstance();
- ClassicSingleton anotherInstance =
- new ClassicSingleton();
- ...
- }
- }
前面這個(gè)代碼片段為何能在沒(méi)有繼承ClassicSingleton并且ClassicSingleton類的構(gòu)造方法是protected的情況下創(chuàng)建其實(shí)例?答案是protected的構(gòu)造方法可以被其子類以及在同一個(gè)包中的其它類調(diào)用。因?yàn)镃lassicSingleton和SingletonInstantiator位于相同的包(缺省的包),所以SingletonInstantiator方法能創(chuàng)建ClasicSingleton的實(shí)例。
這種情況下有兩種解決方案:一是你可以使ClassicSingleton的構(gòu)造方法變化私有的(private)這樣只有ClassicSingleton的方法能調(diào)用它;然而這也意味著ClassicSingleton不能有子類。有時(shí)這是一種很合意的解決方法,如果確實(shí)如此,那聲明你的單例類為final是一個(gè)好主意,這樣意圖明確,并且讓編譯器去使用一些性能優(yōu)化選項(xiàng)。另一種解決方法是把你的單例類放到一個(gè)外在的包中,以便在其它包中的類(包括缺省的包)無(wú)法實(shí)例化一個(gè)單例類。
關(guān)于ClassicSingleton的第三點(diǎn)感興趣的地方是,如果單例由不同的類裝載器裝入,那便有可能存在多個(gè)單例類的實(shí)例。假定不是遠(yuǎn)端存取,例如一些servlet容器對(duì)每個(gè)servlet使用完全不同的類裝載器,這樣的話如果有兩個(gè)servlet訪問(wèn)一個(gè)單例類,它們就都會(huì)有各自的實(shí)例。
第四點(diǎn),如果ClasicSingleton實(shí)現(xiàn)了java.io.Serializable接口,那么這個(gè)類的實(shí)例就可能被序列化和復(fù)原。不管怎樣,如果你序列化一個(gè)單例類的對(duì)象,接下來(lái)復(fù)原多個(gè)那個(gè)對(duì)象,那你就會(huì)有多個(gè)單例類的實(shí)例。
最后也許是最重要的一點(diǎn),就是例1中的ClassicSingleton類不是線程安全的。如果兩個(gè)線程,我們稱它們?yōu)榫€程1和線程2,在同一時(shí)間調(diào)用ClassicSingleton.getInstance()方法,如果線程1先進(jìn)入if塊,然后線程2進(jìn)行控制,那么就會(huì)有ClassicSingleton的兩個(gè)的實(shí)例被創(chuàng)建。
正如你從前面的討論中所看到的,盡管單例模式是最簡(jiǎn)單的設(shè)計(jì)模式之一,在Java中實(shí)現(xiàn)它也是決非想象的那么簡(jiǎn)單。這篇文章接下來(lái)會(huì)揭示Java規(guī)范對(duì)單例模式進(jìn)行的考慮,但是首先讓我們近水樓臺(tái)的看看你如何才能測(cè)試你的單例類。
未完待續(xù)。