建立單例模式(Singleton)的幾種方式

建立單例模式(Singleton)的幾種方式

單例模式多是最經常使用到的設計模式了,可是想要正確的使用單例模式卻並不簡單。
咱們先從最簡單最經常使用的方式開始:html

懶漢式

public class Singleton {
    private static Singleton instance;
    private Singleton (){}
    public static Singleton getInstance() {
     if (instance == null) {
         instance = new Singleton();
     }
     return instance;
    }
}

要點:

  • 私有的靜態內部引用實例java

  • 私有構造函數web

  • 共有靜態的getInstance()方法,當靜態內部引用爲空時才實例化設計模式

缺點:

  • 多線程環境下不安全安全

餓漢式

考慮到多線程的條件,還有另一種經常使用的簡單實現方式:多線程

public class Singleton{
    private final static Singleton instance = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return instance; 
    }
}

要點:

  • private, final 和 static 的實例變量函數

  • 私有化構造函數性能

  • 共有靜態的getInstance()方法優化

  • static 的實例變量在類加載到內存的時候就會初始化,建立實例是線程安全的spa

缺點:

  • 實例在類初始化一開始就被建立了,哪怕後來根本沒有使用它

  • 若是實例的建立時依賴於外部的參數/文件的話,這種方式就不適用了

雙重檢驗鎖

爲了不上面餓漢式的缺點,咱們來考慮改進懶漢式單例模式來支持多線程的狀況。最直接的想法就是對 getInstance()加鎖,可是這樣一來同一時間只能有一個線程調用單例實例,效率低下。經過分析,咱們能夠發現其實不用對整個 getInstance()方法加鎖,只須要在實例爲空須要建立時加鎖。

public static Singleton getInstance() {
     if (instance == null) {
        synchronized (Singleton.class) {
            if (instance == null) {
                 instance = new Singleton();
            }
        }
     }
     return instance;
}

要點:

  • 兩次檢查 instance == null,一次是在同步塊外,一次是在同步塊內

使用兩次判斷的緣由:有可能多個線程同時進入第一個 if 判斷,若是在同步塊中再也不次判斷的話,有可能生成多個實例

缺點:

  • 因爲JVM指令重排序的優化,在instance = new Singleton();仍有可能生成多個實例

在JVM指令優化時,instance = new Singleton();並非一個原子操做,而是3個步驟:
1. 爲instance分配內存
2. 調用 Singleton構造函數初始化成員變量
3. 將instance對象指向分配的內存空間 (instance非null)

在JVM編譯優化時,上面3個步驟並非順序執行的,有可能從新排列執行的順序,有多是 1-2-3, 或者 1-3-2。若是是 1-3-2的執行順序的話,有可能出現這種狀況:線程1執行完了1-3步驟後退出了同步塊,這個時候instance已是非null了,但還沒被初始化,這個時候線程2進入同步塊,判斷instance爲非null,全部直接返回沒有初始化的對象,在後面的使用中天然會報錯。

靜態內部類

public class Singleton{
    private static class SingletonHolder{
        private static final Singleton INSTANE = new Singleton();
    }

    private Singleton(){}

    public static final Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

優勢:

- 使用JVM自己機制保證了線程安全問題;
- SingletonHolder 是私有的,除了 getInstance() 以外沒有辦法訪問它,所以它是懶漢式的;
- 讀取實例的時候不會進行同步,沒有性能缺陷;
- 不依賴 JDK 版本

參考資料

Java中的五種單例模式實現方法
單例模式的七種寫法
單例模式的幾種實現方式
如何正確地寫出單例模式
Java線程安全的單例模式的幾種實現
單例模式的5種形式

相關文章
相關標籤/搜索