單例模式,顧名思義就是在內存中只有一個類的對象實例,對於要佔用重要系統資源的對象,我們常採用單例模式,比如Web中的Servlet,Hibernate中的sessionFectory等都是採用的單例模式。
單例模式有多重實現方式,但這些實現方式中都有一下共同點:
- 有一個私有的無參構造函數,這可以防止其他類實例化它,而且單例類也不應該被繼承,如果單例類允許繼承那麼每個子類都可以創建實例,這就違背了Singleton模式“唯一實例”的初衷。
- 單例類被定義爲sealed,就像前面提到的該類不應該被繼承,所以爲了保險起見可以把該類定義成不允許派生,但沒有要求一定要這樣定義。
- 一個靜態的變量用來保存單實例的引用。
- 一個公有的靜態方法用來獲取單實例的引用,如果實例爲null即創建一個。
單例模式的幾種實現方式:
方式一:餓漢式單例
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
}
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
<span style="font-size:18px;"></span>
這是一種非線程安全的單例模式,可進行如下修改使其線程安全
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
<span style="font-size:18px;"><strong></strong></span>
這種寫法能夠在多線程中很好的工作,而且看起來它也具備很好的lazy loading,但是,遺憾的是,效率很低,99%情況下不需要同步。
方法三:靜態內部類
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
<span style="font-size:18px;"><span></span></span>
這種方式同樣保證初始化instance時只有一個線程,它跟方式一的不同在於:方式一是隻要Singleton類被裝載了,那麼instance就會被實例化(沒有達到lazy loading效果),而這種方式是Singleton類被裝載了,instance不一定被初始化。因爲SingletonHolder類沒有被主動使用,只有顯示通過調用getInstance方法時,纔會顯示裝載SingletonHolder類,從而實例化instance。想象一下,如果實例化instance很消耗資源,我想讓他延遲加載,另外一方面,我不希望在Singleton類加載時就實例化,因爲我不能確保Singleton類還可能在其他的地方被主動使用從而被加載,那麼這個時候實例化instance顯然是不合適的。這個時候,這種方式相比方式一就顯得很合理。
單例模式的優點
- 在內存中只有一個對象,節省內存空間。
- 避免頻繁的創建銷燬對象,可以提高性能。
- 避免對共享資源的多重佔用。
- 可以全局訪問。
適用場景:由於單例模式的以上優點,所以是編程中用的比較多的一種設計模式。我總結了一下我所知道的適合使用單例模式的場景:
- 需要頻繁實例化然後銷燬的對象。
- 創建對象時耗時過多或者耗資源過多,但又經常用到的對象。
- 有狀態的工具類對象。
- 頻繁訪問數據庫或文件的對象。
- 以及其他我沒用過的所有要求只有一個對象的場景。
單例模式注意事項:
- 只能使用單例類提供的方法得到單例對象,不要使用反射,否則將會實例化一個新對象。
- 不要做斷開單例類對象與類中靜態引用的危險操作。
- 多線程使用單例使用共享資源時,注意線程安全問題。
在一個jvm中會出現多個單例嗎
在分佈式系統、多個類加載器、以及序列化的的情況下,會產生多個單例,這一點是無庸置疑的。那麼在同一個jvm中,會不會產生單例呢?使用單例提供的getInstance()方法只能得到同一個單例,除非是使用反射方式,將會得到新的單例。代碼如下
這樣,每次運行都會產生新的單例對象。所以運用單例模式時,一定注意不要使用反射產生新的單例對象。以上也是反射機制的一個缺點,反射的權限太高了,它甚至可以訪問類中的私有方法,這無疑會破壞類的封裝性。