單例模式解析

其中單例(Singleton)模式應該是我們耳熟能詳的一種模式。本文將比較特別的介紹java設計模式中的單例模式。

概念

單例模式,又稱單件模式或者單子模式,指的是一個類只有一個實例,並且提供一個全局訪問函數。

實現思路

  • 在單例的類中設置一個private靜態變量sInstance,sInstance類型爲當前類,用來持有單例唯一的實例。
  • 將(無參數)構造器設置爲private,避免外部使用new構造多個實例。
    其中上面的單例實例有一下幾種實現方式,每種實現方式都需要保證實例的唯一性

餓漢式

餓漢式指的是單例的實例在類裝載時進行創建。如果單例類的構造方法中沒有包含過多的操作處理,餓漢式其實是可以接受的。

餓漢式的常見代碼如下,當SingleInstance類加載時會執行private static SingleInstance sInstance = new SingleInstance();初始化了唯一的實例,然後getInstance()直接返回sInstance。

public class SingleInstance {
  private static SingleInstance sInstance = new SingleInstance();

  private SingleInstance() {
  }

  public static SingleInstance getInstance() {
      return sInstance;
  }
}
餓漢式的問題
  • 因爲是類加載時初始化,如果構造方法中存在過多的處理,會導致加載這個類時比較慢,可能引起性能問題。
  • 如果只進行了類的裝載,並沒有實質的調用,會造成資源的浪費。

懶漢式

懶漢式指的是單例實例在第一次使用時進行創建。這種情況下避免了上面餓漢式可能遇到的問題。

但是考慮到多線程的併發操作,我們不能簡簡單單得像下面代碼實現。

public class SingleInstance {
  private static SingleInstance sInstance;
  private SingleInstance() {
  }

  public static SingleInstance getInstance() {
      if (null == sInstance) {
          sInstance = new SingleInstance();
      }
      return sInstance;
  }
}

上述的代碼在多個線程密集調用getInstance時,存在創建多個實例的可能。比如線程A進入null == sInstance這段代碼塊,而在A線程未創建完成實例時,如果線程B也進入了該代碼塊,必然會造成兩個實例的產生。

synchronized修飾符

使用synchrnozed修飾getInstance方法可能是最簡單的一個保證多線程保證單例唯一性的方法。
synchronized修飾的方法後,當某個線程進入調用這個方法,該線程只有當其他線程離開當前方法後纔會進入該方法。所以可以保證getInstance在任何時候只有一個線程進入。

public class SingleInstance {
  private static SingleInstance sInstance;
  private SingleInstance() {
  }

  public static synchronized SingleInstance getInstance() {
      if (null == sInstance) {
          sInstance = new SingleInstance();
      }
      return sInstance;
  }
}

但是使用synchronized修飾getInstance方法後必然會導致性能下降,而且getInstance是一個被頻繁調用的方法。雖然這種方法能解決問題,但是不推薦。

雙重檢查加鎖

使用雙重檢查加鎖,首先進入該方法時進行null == sInstance檢查,如果第一次檢查通過,即沒有實例創建,則進入synchronized控制的同步塊,並再次檢查實例是否創建,如果仍未創建,則創建該實例。

雙重檢查加鎖保證了多線程下只創建一個實例,並且加鎖代碼塊只在實例創建的之前進行同步。如果實例已經創建後,進入該方法,則不會執行到同步塊的代碼。

public class SingleInstance {
  private static volatile SingleInstance sInstance;
  private SingleInstance() {
  }

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

volatile

Volatile是輕量級的synchronized,它在多處理器開發中保證了共享變量的“可見性”。可見性的意思是當一個線程修改一個共享變量時,另外一個線程能讀到這個修改的值。使用volatile修飾sInstance變量之後,可以確保多個線程之間正確處理sInstance變量。
關於volatile,可以訪問深入分析Volatile的實現原理瞭解更多。

利用static機制

在Java中,類的靜態初始化會在類被加載時觸發,我們利用這個原理,可以實現利用這一特性,結合內部類,可以實現如下的代碼,進行懶漢式創建實例。

public class SingleInstance {
  private SingleInstance() {
  }

  public static SingleInstance getInstance() {
      return SingleInstanceHolder.sInstance;
  }

  private static class SingleInstanceHolder {
      private static SingleInstance sInstance = new SingleInstance();
  }
}

關於這種機制,可以具體瞭解雙重檢查鎖定與延遲初始化

單例 vs static變量

全局靜態變量也可以實現單例的效果,但是使用全局變量無法保證只創建一個實例,而且使用全局變量的形式,需要團隊的約束,執行起來可能會出現問題。

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