意圖:保證一個類隻有一個執行個體,并提供通路它的全局變量。
動機:例如一個會計系統隻能專用于一個公司。為保證沒有其他執行個體對象可以被建立,并且可以提供一個通路該執行個體的方法。
适用性:當類隻能被一個執行個體而且客戶可以從一個衆所周知的通路點通路它時。
當這個唯一執行個體應該是通過子類化可擴充,并且客戶應該無須更改代碼就能使用一個拓展的執行個體時。
單例模式結構圖:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICdzFWRoRXdvN1LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX9UFRNVTRU1UeNpWTmZEWjZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TMwEzN0AjMxIjNwgDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
協作:使用者隻能通過getInstance操作通路執行個體對象。
單例模式有三個要點:一是某個類隻能有一個執行個體;二是它必須自行建立這個執行個體;三是它必須自行向整個系統提供這個執行個體。
Singleton(單例):在單例類的内部實作隻生成一個執行個體,同時它提供一個靜态的getInstance()工廠方法,讓客戶可以通路它的唯一執行個體;為了防止在外部對其執行個體化,将其構造函數設計為私有;在單例類内部定義了一個Singleton類型的靜态對象,作為外部共享的唯一執行個體。
單例模式的建立方法:
餓漢式:線程安全,直接建立對象,造成浪費。
class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { }
public static EagerSingleton getInstance() {
return instance;
}
}
懶漢式:懶漢式單例在第一次調用getInstance()方法時執行個體化,在類加載時并不自行執行個體化,這種技術又稱為延遲加載(Lazy Load)技術,即需要的時候再加載執行個體,多個線程同時調用getInstance()方法會出現線程不安全問題。
class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
synchronized public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
懶漢式加鎖:該懶漢式單例類在getInstance()方法前面增加了關鍵字synchronized進行線程鎖,以處理多個線程同時通路的問題。但是,上述代碼雖然解決了線程安全問題,但是每次調用getInstance()時都需要進行線程鎖定判斷,在多線程高并發通路環境中,将會導緻系統性能大大降低。
class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
synchronized public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
如何既解決線程安全問題又不影響系統性能呢?我們繼續對懶漢式單例進行改進。事實上,我們無須對整個getInstance()方法進行鎖定,隻需對其中的代碼“instance = new LazySingleton();”進行鎖定即可。是以getInstance()方法可以進行如下改進:
class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
instance = new LazySingleton();
}
}
return instance;
}
}
雙重檢查鎖定:假如在某一瞬間線程A和線程B都在調用getInstance()方法,此時instance對象為null值,均能通過instance == null的判斷。由于實作了synchronized加鎖機制,線程A進入synchronized鎖定的代碼中執行執行個體建立代碼,線程B處于排隊等待狀态,必須等待線程A執行完畢後才可以進入synchronized鎖定代碼。但當A執行完畢時,線程B并不知道執行個體已經建立,将繼續建立新的執行個體,導緻産生多個單例對象,違背單例模式的設計思想,是以需要進行進一步改進,在synchronized中再進行一次(instance == null)判斷。
class LazySingleton {
private volatile static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
//第一重判斷
if (instance == null) {
//鎖定代碼塊
synchronized (LazySingleton.class) {
//第二重判斷
if (instance == null) {
instance = new LazySingleton(); //建立單例執行個體
}
}
}
return instance;
}
}
(靜态内部類)由于靜态單例對象沒有作為Singleton的成員變量直接執行個體化,是以類加載時不會執行個體化Singleton,第一次調用getInstance()時将加載内部類HolderClass,在該内部類中定義了一個static類型的變量instance,此時會首先初始化這個成員變量,由Java虛拟機來保證其線程安全性,確定該成員變量隻能初始化一次。由于getInstance()方法沒有任何線程鎖定,是以其性能不會造成任何影響。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚舉單例模式:
public class EnumSingleton{
private EnumSingleton(){}
public static EnumSingleton getInstance(){
return Singleton.INSTANCE.getInstance();
}
private static enum Singleton{
INSTANCE;
private EnumSingleton singleton;
//JVM會保證此方法絕對隻調用一次
private Singleton(){
singleton = new EnumSingleton();
}
public EnumSingleton getInstance(){
return singleton;
}
}
}
1.主要優點
單例模式的主要優點如下:
(1) 單例模式提供了對唯一執行個體的受控通路。因為單例類封裝了它的唯一執行個體,是以它可以嚴格控制客戶怎樣以及何時通路它。
(2) 由于在系統記憶體中隻存在一個對象,是以可以節約系統資源,對于一些需要頻繁建立和銷毀的對象單例模式無疑可以提高系統的性能。
(3) 允許可變數目的執行個體。基于單例模式我們可以進行擴充,使用與單例控制相似的方法來獲得指定個數的對象執行個體,既節省系統資源,又解決了單例單例對象共享過多有損性能的問題。
2.主要缺點
單例模式的主要缺點如下:
(1) 由于單例模式中沒有抽象層,是以單例類的擴充有很大的困難。
(2) 單例類的職責過重,在一定程度上違背了“單一職責原則”。因為單例類既充當了工廠角色,提供了工廠方法,同時又充當了産品角色,包含一些業務方法,将産品的建立和産品的本身的功能融合到一起。
(3) 現在很多面向對象語言(如Java、C#)的運作環境都提供了自動垃圾回收的技術,是以,如果執行個體化的共享對象長時間不被利用,系統會認為它是垃圾,會自動銷毀并回收資源,下次利用時又将重新執行個體化,這将導緻共享的單例對象狀态的丢失。