單例模式
單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。它有以下三個特點:
- 單例類只能有一個實例。
- 單例類必須自己創建自己的唯一實例。
- 單例類必須給所有其他對象提供這一實例。
創建單例模式
- 懶漢式(線程不安全)
所謂的懶漢式就是需要使用的時候纔去創建實例。這種非線程安全的創建模式,懶加載很明顯,不能夠滿足多線程條件下使用。
/**
* 傳統的懶漢式單例
*/
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 懶漢式(線程安全)
通過synchronized關鍵字可以實現線程安全的懶漢式單例,但是似乎還有更好的方法。
/**
* 線程安全的懶漢式單例
*/
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
- 餓漢式(線程安全)
餓漢式即在類裝載的時候就進行了實例化。但是很這樣會導致無法懶加載,效率很低,浪費系統資源。
/**
* 線程安全的餓漢式單例
*/
public class Singleton {
// 類加載時就初始化
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
餓漢式的創建方式在一些場景中將無法使用:譬如 Singleton 實例的創建是依賴參數或者配置文件的,在 getInstance() 之前必須調用某個方法設置參數給它,那樣這種單例寫法就無法使用了。
- 雙重校驗鎖
雙重檢驗鎖模式(double checked locking pattern),是一種使用同步塊加鎖的方法。程序員稱其爲雙重檢查鎖,因爲會有兩次檢查 instance == null,一次是在同步塊外,一次是在同步塊內。爲什麼在同步塊內還要再檢驗一次?因爲可能會有多個線程一起進入同步塊外的 if,如果在同步塊內不進行二次檢驗的話就會生成多個實例了。
/**
* 線程安全的雙重校驗鎖單例
*/
public class Singleton {
private volatile static Singleton instance;// 1
private Singleton() {
something init;
// 注意如果不在 1 處加上volatile,(雙重校驗鎖)這裏可能出現空指針異常。
// 原因是JVM運行時的指令重排序,導致線程2判斷instance不爲空的時候,就返回了上面的init實例變量,但是此時實例變量可能未創建完成。
}
public static Singleton getInstance() {
// 先檢查實例是否存在,如果不存在才進入下面的同步塊
if (null == instance) {
// 同步塊,線程安全地創建實例
synchronized (Singleton.class) {
// 再次檢查實例是否存在,如果不存在才真正地創建實例
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
- 靜態內部類
靜態內部類相比餓漢式創建單例有一處優點,就是創建實例的時候並不是在類裝載的時候。而是主動通過SingletonHolder類,調用getInstance方法的時候,纔會去實例化。這樣就很合理的節省資源。
/**
* 線程安全的靜態內部類單例
*/
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
public static final Singleton getInstance() {
return SingletonHolder.instance;
}
}
這種寫法仍然使用JVM本身機制保證了線程安全問題;由於 SingletonHolder 是私有的,除了 getInstance() 之外沒有辦法訪問它,因此它是懶漢式的;同時讀取實例的時候不會進行同步,沒有性能缺陷;也不依賴 JDK 版本。
- Holder方式
A類初始化是不會創建A的實例,而是在B類中靜態的聲明瞭對A的實例化,然後再A中,需要實例化A時去調用B。
- 枚舉方式