【設計模式】單例模式(Singleton)
思想
保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。
類圖
Singleton 類稱為單例類,該類的建構函式是 Private 的,這是為了禁止從 Singleton 類的外部呼叫建構函式,這就堵死了外界利用 new 建立此類的可能。通過 getInstance 方法獲得本類例項的唯一全域性訪問點。
實現
1、餓漢式 - 執行緒安全 [可用]
當類被載入時,靜態變數 instance 會被初始化,此時類的私有函式會被呼叫,單例類的唯一例項將被建立。
class Singleton {
private static Singleton instance = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return instance;
}
}
優點: 寫法比較簡單,在類裝載的時候就完成例項化,避免了執行緒同步問題。
缺點: 在類裝載的時候就完成例項化,沒有達到 Lazy Loading 的效果。如果從始至終從未使用過這個例項,則會造成記憶體的浪費。
2、懶漢式 - 執行緒不安全 [不可用]
這種寫法起到了Lazy Loading的效果,但是隻能在單執行緒下使用。
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
3、懶漢式 - 執行緒安全 同步方法 [不推薦]
雖然解決了執行緒不安全問題,但是效率很低,每個執行緒在想獲得類的例項時候,執行getInstance()方法都要進行同步。
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
4、懶漢式 - 執行緒安全 同步程式碼塊 [不可用]
由於上一種實現方式同步效率太低,所以摒棄同步方法,改為同步產生例項化的的程式碼塊,但是這種同步並不能起到執行緒同步的作用。假如一個執行緒進入了 if (singleton == null)
判斷語句塊,還未來得及往下執行,另一個執行緒也通過了這個判斷語句,這時便會產生多個例項。
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
5、雙重校驗鎖(DCL) - 執行緒安全 [可用]
單例在第一次呼叫 getInstance
方法時例項化,在類載入時不自行例項化,即需要的時候再載入例項,為了處理多個執行緒同時訪問的問題,加入雙重鎖機制。
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;
}
}
6、靜態內部類 [推薦]
餓漢式單例類不能實現延遲載入,不管將來用不用始終佔據記憶體;懶漢式單例類執行緒安全控制煩瑣,而且效能受影響。
IoDH(Initialization Demand Holder) 技術能夠克服單例的上述缺點,在單例類中增加一個靜態內部類,在該內部類中建立單例物件,再將該單例物件通過 getInstance
方法返回給外部使用。
避免了執行緒不安全,延遲載入,效率高。
class Singleton {
private Singleton() { }
private static class HolderClass {
private final static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return HolderClass.instance;
}
}
7、列舉 [推薦]
這種實現方式還沒有被廣泛採用,但這是實現單例模式的最佳方法。它更簡潔,自動支援序列化機制,絕對防止多次例項化,防止反序列化重新建立新的物件。
public enum Singleton {
INSTANCE;
public void whateverMethod() { }
}
分析
1、優點
系統記憶體中該類只存在一個物件,節省了系統資源,對於一些需要頻繁建立銷燬的物件,使用單例模式可以提高系統性能。
2、缺點
當想例項化一個單例類的時候,必須要記住使用相應的獲取物件的方法,而不是使用new,可能會給其他開發人員造成困擾,特別是看不到原始碼的時候。
3、適用環境
- 需要頻繁的進行建立和銷燬的物件
- 建立物件時耗時過多或耗費資源過多,但又經常用到的物件
- 工具類物件
- 頻繁訪問資料庫或檔案的物件
4、經驗
一般情況下,不建議使用第 2、3、4 種懶漢方式,建議使用第 1 種餓漢方式。只有在要明確實現 Lazy Loading 效果時,才會使用第 6 種靜態內部類。如果涉及到反序列化建立物件時,可以嘗試使用第 7 種列舉方式。如果有其他特殊的需求,可以考慮使用第 5 種雙重校驗鎖。
5、應用
- java.lang.Runtime#getRuntime()
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
}
- java.awt.Toolkit#getDefaultToolkit()
不需要事先建立好,只要在第一次真正用到的時候再建立就可以了,所以使用懶漢式[同步方法]。
- java.awt.Desktop#getDesktop()