單例設計模式詳解
對於系統中的某些類來說,只有一個實例很重要,例如,一個系統中可以存在多個打印任務,但是只能有一個正在工作的任務;一個系統只能有一個窗口管理器或文件系統;一個系統只能有一個計時工具或ID(序號)生成器。如何保證一個類只有一個實例並且這個實例易於被訪問呢?定義一個全局變量可以確保對象隨時都可以被訪問,但不能防止我們實例化多個對象。一個更好的解決辦法是讓類自身負責保存它的唯一實例。這個類可以保證沒有其他實例被創建,並且它可以提供一個訪問該實例的方法。這就是單例模式的模式動機。
模式定義
單例模式(Singleton Pattern):單例模式確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例,這個類稱爲單例類,它提供全局訪問的方法。單例模式的要點有三個:一是某個類只能有一個實例;二是它必須自行創建這個實例;三是它必須自行向整個系統提供這個實例。單例模式是一種對象創建型模式。單例模式又名單件模式或單態模式。
- Singleton:單例
時序圖
public static synchronized Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public static Singleton getSingleton(){ if (instance == null) { //Single Checked synchronized (Singleton.class) { if (instance == null) { //Double Checked instance = new Singleton(); } } } return instance ; } |
instance
== null
,一次是在同步塊外,一次是在同步塊內。在同步塊內還要再檢驗一次,是因爲可能會有多個線程一起進入同步塊外的 if,如果在同步塊內不進行二次檢驗的話就會生成多個實例了。
這段代碼看起來很完美,但還是有問題。主要在於instance = new Singleton()
這句,這並非是一個原子操作,事實上在
JVM 中這句話大概做了下面 3 件事情。
- 給 instance 分配內存
- 調用 Singleton 的構造函數來初始化成員變量
- 將instance對象指向分配的內存空間(執行完這步 instance 就爲非 null 了)
但是在 JVM 的即時編譯器中存在指令重排序的優化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執行順序可能是 1-2-3 也可能是 1-3-2。如果是後者,則在3 執行完畢、2 未執行之前,被線程二搶佔了,這時 instance 已經是非 null 了(但卻沒有初始化),所以線程二會直接返回instance,然後使用就會報錯。
只需要將 instance 變量聲明成 volatile就可以了。
public class Singleton {
private volatile static Singleton instance; //聲明成 volatile
private Singleton(){}
public static Singleton getSingleton(){
<span style="white-space:pre"> </span>if (instance == null) {
<span style="white-space:pre"> </span>synchronized (Singleton.class) {
<span style="white-space:pre"> </span>if (instance == null) {
instance = new Singleton();
}
}
}
<span style="white-space:pre"> </span>return instance;
}
}
public class Singleton{ //類加載時就初始化 private static final Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return instance; } }
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 enum EasySingleton{ INSTANCE; } |
- 單例模式確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例,這個類稱爲單例類,它提供全局訪問的方法。單例模式的要點有三個:一是某個類只能有一個實例;二是它必須自行創建這個實例;三是它必須自行向整個系統提供這個實例。單例模式是一種對象創建型模式。
- 單例模式只包含一個單例角色:在單例類的內部實現只生成一個實例,同時它提供一個靜態的工廠方法,讓客戶可以使用它的唯一實例;爲了防止在外部對其實例化,將其構造函數設計爲私有。
- 單例模式的目的是保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。單例類擁有一個私有構造函數,確保用戶無法通過new關鍵字直接實例化它。除此之外,該模式中包含一個靜態私有成員變量與靜態公有的工廠方法。該工廠方法負責檢驗實例的存在性並實例化自己,然後存儲在靜態成員變量中,以確保只有一個實例被創建。
- 單例模式的主要優點在於提供了對唯一實例的受控訪問並可以節約系統資源;其主要缺點在於因爲缺少抽象層而難以擴展,且單例類職責過重。
- 單例模式適用情況包括:系統只需要一個實例對象;客戶調用類的單個實例只允許使用一個公共訪問點。