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 static Singleton3 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 ( ) ;
複製程式碼
note:免費的才是最貴的