設計模式(四)Singleton設計模式
Singleton單例模式是最簡單的設計模式,它的主要作用是保證在程式執行生命週期中,使用了單例模式的類只能有一個例項物件存在。單例模式實現了類似C語言中全域性變數的功能,單例模式常用於註冊/查詢的服務。
單例模式的UML圖如下:

單例模式有兩種實現方式:飽漢模式和餓漢模式,如下:
1.飽漢單例模式例子程式碼:
public class Singleton1{ //飽漢模式,宣告時就建立例項物件 public static final Singleton1 instance = new Singleton1(); //單類模式的構造方法必須為private,以避免通過構造方法建立物件例項, //並且必須顯示宣告構造方法,以防止使用預設構造方法 private Singleton1(){} //單類模式必須對外提供獲取例項物件的方法 public static Singleton1 geInstance(){ return instance; } } 2.餓漢單例模式即延遲初始化單例方式,例子程式碼: public class Singleton2{ //餓漢模式,宣告時不建立例項物件 public static Singleton2 instance; //單類模式的構造方法必須為private,以避免通過構造方法建立物件例項, //並且必須顯示宣告構造方法,以防止使用預設構造方法 private Singleton2(){} //單類模式必須對外提供獲取例項物件的方法,延遲初始化的單類模式必須使用synchronized同步關鍵字,否則多執行緒情況下很容易產生多個例項物件 public static synchronized Singleton2 geInstance(){ //延遲初始化,只有當第一次使用時才建立物件例項 if(instance == null){ instance = new Singleton2(); } return instance; } } 一般認為飽漢模式要比餓漢模式更加安全。 上面兩種Singleton單例設計模式的實現方式都隱藏有如下的問題: (1).雖然構造方式的訪問修飾符為private,即除了自身以外其他任何類都無法呼叫,但是通過反射機制的setAccessiable(true)方法可以訪問私有方法和屬性。因此Singleton單例模式必須考慮這種例外情況。 (2).物件序列化之後再反序列化時會生成新的物件,因此當Singleton單例模式類實現序列化介面時,必須顯式宣告所有的欄位為tranisent,並且提供如下的readResolve方法來防止通過序列化破壞單態模式: private Object readResolve(){ return INSTANCE; } 3.使用Lazy initialization holder class模式實現單態: public class Singleton3 { /** * 類級的內部類,也就是靜態的成員式內部類,該內部類的例項與外部類的例項 * 沒有繫結關係,而且只有被呼叫到才會裝載,從而實現了延遲載入 */ private static class SingletonHolder{ //靜態初始化器,由JVM來保證執行緒安全 private static Singleton3 instance = new Singleton3(); } //私有化構造方法 private Singleton3(){ } public staticSingleton3 getInstance(){ return SingletonHolder.instance; } } 當getInstance方法第一次被呼叫的時候,它第一次讀取SingletonHolder.instance,導致SingletonHolder類得到初始化;而這個類在裝載並被初始化的時候,會初始化它的靜態域,從而建立Singleton的例項,由於是靜態的域,因此只會被虛擬機器在裝載類的時候初始化一次,並由虛擬機器來保證它的執行緒安全性。 這個模式的優勢在於,getInstance方法並沒有被同步,並且只是執行一個域的訪問,因此延遲初始化並沒有增加任何訪問成本。 4.在JDK1.5之後引入了Enum列舉,因此在JDK1.5之後Singleton單例模式又有了第三種實現方式,也是最好的實現方式,例子如下: public enum Singleton4{ INSTANCE{ public void doSomething(){ ... } }; public abstract void doSomething(); } Singleton單例模式中只有一個INSTANCE列舉元素,列舉可以保證真個程式生命週期中只有一個例項物件存在,同時還避免了常規Singleton單例模式private構造方法被反射呼叫和序列化問題(列舉提供了序列化保證機制,確保多次序列化和反序列化不會建立多個例項物件)。 注意:java中除了構造方法可以建立物件例項以外,還可以通過克隆方法(clone()是Object中的protected方法)來建立物件,若單例物件直接繼承自Object物件,則如果沒有提供具體clone方法實現,則當呼叫克隆方法建立物件時,會丟擲執行時的異常CloneNotSupportedException。 若單例類繼承了實現克隆方法的類,則在單例類中必須覆蓋父類的克隆方法,顯式丟擲異常CloneNotSupportedException。 另外,實現了單例模式的類不能再有派生子類,因為構造方式是私有的,子類無法呼叫父類構造方法,因此達到了Final的效果。 JDK的中單例模式的應用: java.lang.Runtime 單例模式的優點: * 在記憶體中只有一個物件,節省記憶體空間。 * 避免頻繁的建立銷燬物件,可以提高效能。 * 避免對共享資源的多重佔用。 * 可以全域性訪問。 適用場景:由於單例模式的以上優點,所以是程式設計中用的比較多的一種設計模式。我總結了一下我所知道的適合使用單例模式的場景: * 需要頻繁例項化然後銷燬的物件。 * 建立物件時耗時過多或者耗資源過多,但又經常用到的物件。 * 有狀態的工具類物件。 * 頻繁訪問資料庫或檔案的物件。 * 以及其他我沒用過的所有要求只有一個物件的場景。 單例模式注意事項: * 只能使用單例類提供的方法得到單例物件,不要使用反射,否則將會例項化一個新物件。 * 不要做斷開單例類物件與類中靜態引用的危險操作。 * 多執行緒使用單例使用共享資源時,注意執行緒安全問題。 關於java中單例模式的一些爭議: 單例模式的物件長時間不用會被jvm垃圾收集器收集嗎? 看到不少資料中說:如果一個單例物件在記憶體中長久不用,會被jvm認為是一個垃圾,在執行垃圾收集的時候會被清理掉。對此這個說法,我持懷疑態度我的觀點是:在hotspot虛擬機器1.6版本中,除非人為地斷開單例中靜態引用到單例物件的聯接,否則jvm垃圾收集器是不會回收單例物件的。 在一個jvm中會出現多個單例嗎? 在分散式系統、多個類載入器、以及序列化的的情況下,會產生多個單例,這一點是無庸置疑的。那麼在同一個jvm中,會不會產生單例呢?使用單例提供的getInstance()方法只能得到同一個單例,除非是使用反射方式,將會得到新的單例。程式碼如下 複製程式碼
Class c = Class . forName ( Singleton . class . getName ( ) ) ; Constructor ct = c . getDeclaredConstructor ( ) ; ct . setAccessible ( true ) ; Singleton singleton = ( Singleton ) ct . newInstance ( ) ; 複製程式碼