定義
Java中單例模式定義:“一個類有且僅有一個實例,並且自行實例化向整個系統提供。”
寫法
1.餓漢式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
這種方式在類加載時就完成了初始化,所以類加載較慢,但獲取對象的速度快。這種方式避免了多線程的同步問題。在類加載的時候就完成實例化,沒有達到懶加載的效果。如果從始至終未使用過這個實例,則會造成內存的浪費。
2.懶漢式(線程不安全)
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懶漢模式聲明瞭一個靜態對象,在第一次調用時初始化。這雖然節約了資源,但第一次加載時需要實例化,反應稍慢一些,而且在多線程時不能正常工作。
3.懶漢式(線程安全)
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
種寫法能夠在多線程中很好地工作,但是每次調用getInstance方法時都需要進行同步。這會造成不必要的開銷,而且大部分時候我們是用不到同步的。所以,不太建議用這種模式。
4.雙重檢查模式(DCL)
public class Singleton {
private volatile static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
這種寫法在getInstance方法中對Singleton進行了兩次判空:第一次是爲了不必要的同步,第二次是在instance等於null的情況下才創建實例。
DCL的優點是資源利用率高。第一次執行getInstance時單例對象才被實例化,效率高。其缺點是第一次加載時反應稍慢一些,在高併發環境下也有一定的缺陷。DCL雖然在一定程度上解決了資源的消耗和多餘的同步、線程安全等問題,但其還是在某些情況會出現失效的問題,也就是DCL失效。這裏建議用靜態內部類單例模式來替代DCL。
這裏爲什麼要使用volatile呢?
上面這種寫法看似很完美,但實際上仍然存在一個問題——當instance不爲null時,可能指向一個"被部分初始化的對象"。問題出在這行簡單的賦值語句:
instance = new Singleton();
它並不是一個原子操作。可以”抽象“爲下面幾條JVM指令:
memory = allocate(); //1:分配對象的內存空間
ctorInstance(memory); //2:初始化對象
instance = memory; //3:設置instance指向剛分配的內存地址
上面2依賴於1,但是3並不依賴於2,所以JVM可以以“優化”爲目的對它們進行重排序,經過重排序後如下:
memory = allocate(); //1:分配對象的內存空間
instance = memory; //3:設置instance指向剛分配的內存地址(此時對象還未初始化)
ctorInstance(memory); //2:初始化對象
可以看到指令重排序之後, 3 排在了 2 之前,即引用instance指向內存memory時,這段嶄新的內存還沒有初始化——即,引用instance指向了一個"被部分初始化的對象"。此時,如果另一個線程調用getInstance方法,由於instance已經指向了一塊內存空間,從而if條件判爲false,方法返回instance引用,用戶得到了沒有完成初始化的“半個”單例。
解決該問題,只需要將instance聲明爲volatile變量。在這裏使用volatile會或多或少地影響性能,但考慮到程序的正確性,犧牲這點性能還是值得的。
5.靜態內部類單例
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonLazyHolder.sInstance;
}
private static class SingletonLazyHolder {
private static final Singleton sInstance = new Singleton();
}
}
第一次加載Singleton類時並不會初始化sInstance,只有第一次調用getInstance方法時虛擬機加載SingletonLazyHolder 並初始化 sInstance。這樣不僅能確保線程安全,也能保證Singleton 類的唯一性。所以,推薦使用靜態內部類單例模式。
6.枚舉單例
public enum Singleton {
INSTANCE
/*其他方法*/
}
上面就是枚舉單例模式,需要小心的是如果你在使用實例方法,那麼你需要確保線程安全。默認枚舉實例的創建是線程安全的,但是在枚舉中的其他任何方法由開發人員自己處理。枚舉單例的優點就是簡單,但是大部分應用開發很少用枚舉,其可讀性並不是很高。
使用場景
單例模式可能的使用場景如下:
1.整個項目需要一個共享訪問點或共享數據。
2.創建一個對象需要耗費的資源過多,比如訪問I/O或者數據庫等資源。
3.工具類對象等。