大話設計模式——單例模式

在開發過程中,其實很多情況下,都需要用到單例模式來維持對象的唯一性。
比如線程池、數據源、sessionFactory等。

一般的做法(懶漢式):

public class MyClass{
  //餓漢式是直接聲明變量是就初始化:
  //private static MyClass myClass=new MyClass();
  private static MyClass myClass;
  //將構造器聲明爲私有的,不允許外部類創建實例對象
  private MyClass(){}

  //聲明一個靜態方法來返或一個單例對象
  public static MyClass getInstance(){
    if(myClass == null)
       myClass = new MyClass();
     return myClass;
  }

}

但是這個會在多線程下回導致數據不一致性。
這裏寫圖片描述
這樣線程一跟線程二就獲得不同的實例對象,打破了唯一性。

這個問題就變爲多線程問題,一般通過加鎖是可以解決問題的,比如:

public class MyClass{
  private static MyClass myClass;
  //將構造器聲明爲私有的,不允許外部類創建實例對象
  private MyClass(){}

  //聲明一個靜態方法來返或一個單例對象       
  //通過增加synchronized來保證同一時刻只有一個線程可以執行getInstance()函數
  public static synchronized MyClass getInstance(){
    if(myClass == null)
       myClass = new MyClass();
     return myClass;
  }
}

但是這種加鎖方法會帶來性能問題,因爲其實對於單例模式,只有在第一次初始化myClass的時候需要控制只有一個線程可以進行實例化對象,對於之後的獲得對象,由於已經實例化了,是可以直接返回的,但是由於synchronized的聲明就降低了性能。

這樣就引出了“雙重檢查加鎖”概念,代碼如下:

public class MyClass{
  //增加volatile關鍵字來修飾"實例變量"
  private volatile static MyClass myClass;
  //將構造器聲明爲私有的,不允許外部類創建實例對象
  private MyClass(){}

  //聲明一個靜態方法來返或一個單例對象       
  //通過增加synchronized來保證同一時刻只有一個線程可以執行getInstance()函數
  //將鎖的粒度降低
  public static MyClass getInstance(){
    if(myClass == null){
        synchronized(this.class){
        if(myClass == null){
          myClass = new MyClass();
          }
     }
      return myClass;
  }
}

這樣就保證了只有當第一次需要實例化對象的時候纔會用到synchronized控制併發。

其實看到這,很多人會有一個疑問,好像沒必要用到volatile關鍵字啊。
我第一次看見也是這麼覺得,後來查看了一下資料,發現了“新大陸”。
主要原因就是編譯器爲了一下優化操作,會執行指令重排。具體原因可以參考博客:雙重檢查鎖定原理詳解
當然單例模式還有很多不同的實現方式,比如枚舉等。

以上內容爲《大話設計模式》的學習筆記

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章