一.單例模式的定義
單例模式(Singleton Pattern):確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例,這個類稱爲單例類,它提供全局訪問的方法。單例模式是一種對象創建型模式。
二.單例模式的應用場景舉例
1. 在windows下的任務管理器實現。
2.編寫Web應用時配置對象的讀取。
3.網頁的計數器。
4.Java中的線程池集合類的設計。
在應用需要對象實現某一功能, 又要避免大規模創建對象造成資源浪費時,可以考慮使用單例模式。其次,在應用需要控制某一資源,使全局可以訪問時可以考慮單例模式。
三.單例模式的實現方法
1.餓漢式單例模式
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance()
{
return instance;
}
}
在類加載的時候加載單例對象,使用這種方法的優點是在多線程的情況下只有一個實例對象,可以保證單例對象的唯一性,並且在使用的時候可以使應用更流暢(因爲之前已經進行過加載);缺點是如果該對象得不到使用那麼該單例模式創建的對象也會一直駐留在內存中。
2.懶漢式單例模式
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
在多線程併發訪問的時候,上述實現是不安全的。假設線程A剛剛執行完判斷instance是否爲空的if語句,時間片段分配給線程B,線程B判斷instance爲空,new出了一個單例對象,然後切換回線程A執行,由於線程A執行過判斷的語句,順序執行將再次創建單例對象。違背了單例模式的設計原則。
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static synchronized Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
在獲取單例對象的getInstance()方法前簡單增加synchronized關鍵字使得在多線程的情況下不會出現多個單例對象,Java的線程池集合也是使用的這種方法來實現的。但是在高併發的情況下每次調用方法時都需要加鎖使得系統性能大大降低。
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance(){
if (instance == null){
synchronized(Singleton.class){
instance = new Singleton();
}
}
return instance;
}
}
只在創建單例對象的時候加鎖可以緩解上述的問題。由於不需要每次調用getInstance()方法時都加鎖,而是隻在創建時加鎖使得性能有很大的提高。但是這段代碼真的能保證在多線程的情況下是安全的麼(在系統中只有一個單例對象)?和前面的分析類似,如果線程A在執行過if語句的判斷之後系統將時間片段交給線程B,那麼系統中仍然會出現兩個單例對象。
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance(){
if (instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
在synchronized塊內加上對instance是否爲空的判斷可以解決上述問題,同時,需要將單例對象聲明爲volatile的,這樣做的目的是可以屏蔽虛擬機的一些優化,但是性能上會有一定降低。
通過上面的代碼可見不論是餓漢式還是懶漢式都有一定的缺點,有沒有一種方法可以實現延時加載並且還不用考慮由多線程問題帶來的性能損耗呢?有的
public class Singleton {
private Singleton() {
}
private static class Holder {
private final static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return Holder.instance;
}
}
在單例類中沒有靜態類Holder的對象,所以在類加載的時候並不會創建單例對象。當調用getInstance()方法時,Holder類進行加載並且創建出單例對象。高效的實現了上述的需求。
在實際的需求中可以根據自己應用的情況來選取單例模式的使用。
四.單例模式的缺點
畢竟不存在完美的模式,單例模式存在着以下幾點不足之處:
1.單例模式違背了“單一職責原則”,在單例類的內部有創建實例的代碼,也有執行功能邏輯的代碼。有可能使單例類過於龐大和冗雜,不利於今後的擴展。
2.在java中存在着垃圾回收機制,在內存不足的情況下單例對象也有可能被系統回收,那麼單例對象內的數據域也就會消失。如果有擔心這個情況發生可以將重要的數據域存儲到本地文件或者數據庫中,在單例對象被回收後可以恢復數據。