單例模式的要點有三個
一是某各類只能有一個實例;
二是它必須自行創建這個事例;
三是它必須自行向整個系統提供這個實例。
還要通過雙重加鎖來保證線程安全問題。(jdk 1.5 以後無缺陷)
轉載自 http://www.yesky.com/60/1723060_1.shtml
單例模式的結構
單例模式有以下的特點:
.. 單例類只可有一個實例。
.. 單例類必須自己創建自己這惟一的實例。
.. 單例類必須給所有其他對象提供這一實例。
雖然單例模式中的單例類被限定只能有一個實例,但是單例模式和單例類可以很容易被推廣到任意且有限多個實例的情況,這時候稱它爲多例模式(Multiton Pattern) 和多例類(Multiton Class),請見"專題:多例(Multiton )模式與多語言支持"一章。單例類的簡略類圖如下所示。
由於Java 語言的特點,使得單例模式在Java 語言的實現上有自己的特點。這些特點主要表現在單例類如何將自己實例化上。
餓漢式單例類餓漢式單例類是在Java 語言裏實現得最爲簡便的單例類,下面所示的類圖描述了一個餓漢式單例類的典型實現。
從圖中可以看出,此類已經自已將自己實例化。
代碼清單1:餓漢式單例類
public class EagerSingleton { private static final EagerSingleton m_instance = new EagerSingleton(); /** * 私有的默認構造子 */ private EagerSingleton() { } /** * 靜態工廠方法 */ public static EagerSingleton getInstance() { return m_instance; } } |
讀者可以看出,在這個類被加載時,靜態變量m_instance 會被初始化,此時類的私有構造子會被調用。這時候,單例類的惟一實例就被創建出來了。
Java 語言中單例類的一個最重要的特點是類的構造子是私有的,從而避免外界利用構造子直接創建出任意多的實例。值得指出的是,由於構造子是私有的,因此,此類不能被繼承。
懶漢式單例類
與餓漢式單例類相同之處是,類的構造子是私有的。與餓漢式單例類不同的是,懶漢式單例類在第一次被引用時將自己實例化。如果加載器是靜態的,那麼在懶漢式單例類被加載時不會將自己實例化。如下圖所示,類圖中給出了一個典型的餓漢式單例類實現。
代碼清單2:懶漢式單例類
package com.javapatterns.singleton.demos; public class LazySingleton { private static LazySingleton m_instance = null; /** * 私有的默認構造子,保證外界無法直接實例化 */ private LazySingleton() { } /** * 靜態工廠方法,返還此類的惟一實例 */ synchronized public static LazySingleton getInstance() { if (m_instance == null) { m_instance = new LazySingleton(); } return m_instance; } } |
讀者可能會注意到,在上面給出懶漢式單例類實現裏對靜態工廠方法使用了同步化,以處理多線程環境。有些設計師在這裏建議使用所謂的"雙重檢查成例"。必須指出的是,"雙重檢查成例"不可以在Java 語言中使用。不十分熟悉的讀者,可以看看後面給出的小節。(這個問題 傳智播客老師在課堂也提到了一下,jdk 1.5以後舊沒有問題了 這篇文章2003-08-21 09:52作者:閻宏)
同樣,由於構造子是私有的,因此,此類不能被繼承。餓漢式單例類在自己被加載時就將自己實例化。即便加載器是靜態的,在餓漢式單例類被加載時仍會將自己實例化。單從資源利用效率角度來講,這個比懶漢式單例類稍差些。
從速度和反應時間角度來講,則比懶漢式單例類稍好些。然而,懶漢式單例類在實例化時, 必須處理好在多個線程同時首次引用此類時的訪問限制問題,特別是當單例類作爲資源控制器,在實例化時必然涉及資源初始化,而資源初始化很有可能耗費時間。這意味着出現多線程同時首次引用此類的機率變得較大。
餓漢式單例類可以在Java 語言內實現, 但不易在C++ 內實現,因爲靜態初始化在C++ 裏沒有固定的順序,因而靜態的m_instance 變量的初始化與類的加載順序沒有保證,可能會出問題。這就是爲什麼GoF 在提出單例類的概念時,舉的例子是懶漢式的。他們的書影響之大,以致Java 語言中單例類的例子也大多是懶漢式的。實際上,本書認爲餓漢式單例類更符合Java 語言本身的特點。
////////////////////////////////////////////
登記式單例類
登記式單例類是GoF 爲了克服餓漢式單例類及懶漢式單例類均不可繼承的缺點而設計的。本書把他們的例子翻譯爲Java 語言,並將它自己實例化的方式從懶漢式改爲餓漢式。只是它的子類實例化的方式只能是懶漢式的, 這是無法改變的。如下圖所示是登記式單例類的一個例子,圖中的關係線表明,此類已將自己實例化。
代碼清單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); } /** * 保護的默認構造子 */ protected RegSingleton() {} /** * 靜態工廠方法,返還此類惟一的實例 */ 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) ); } /** * 一個示意性的商業方法 */ public String about() { return "Hello, I am RegSingleton."; } } |
它的子類RegSingletonChild 需要父類的幫助才能實例化。下圖所示是登記式單例類子類的一個例子。圖中的關係表明,此類是由父類將子類實例化的。
下面是子類的源代碼。
代碼清單4:登記式單例類的子類
import java.util.HashMap; public class RegSingletonChild extends RegSingleton { public RegSingletonChild() {} /** * 靜態工廠方法 */ static public RegSingletonChild getInstance() { return (RegSingletonChild) RegSingleton.getInstance( "com.javapatterns.singleton.demos.RegSingletonChild" ); } /** * 一個示意性的商業方法 */ public String about() { return "Hello, I am RegSingletonChild."; } } |
在GoF 原始的例子中,並沒有getInstance() 方法,這樣得到子類必須調用的getInstance(String name)方法並傳入子類的名字,因此很不方便。本章在登記式單例類子類的例子裏,加入了getInstance() 方法,這樣做的好處是RegSingletonChild 可以通過這個方法,返還自已的實例。而這樣做的缺點是,由於數據類型不同,無法在RegSingleton 提供這樣一個方法。由於子類必須允許父類以構造子調用產生實例,因此,它的構造子必須是公開的。這樣一來,就等於允許了以這樣方式產生實例而不在父類的登記中。這是登記式單例類的一個缺點。
GoF 曾指出,由於父類的實例必須存在纔可能有子類的實例,這在有些情況下是一個浪費。這是登記式單例類的另一個缺點。