設計模式-單例模式

    類和對象的關係很容易理解,對象就是類的一個具體實例。我們每個類可以創建多個對象,這些對象是相互獨立不同的對象。但是有的時候我們的某個類只希望創建一個對象,不需要創建多個對象。比如框架加載在讀取配置文件的時候,讀取並保存配置文件信息的對象我們沒必要創建多個,只要有一個對象讀取到配置信息並保存在對象中,然後所有要使用到配置信息的地方都使用這個對象就可以了。如果有很多個對象讀出來的配置是一樣一樣的,那麼是完全沒必要的,除了浪費資源。再比如我們的工具類、線程池、緩存、日誌對象等等。對於這樣的一些類,我們只希望永遠有且只有一個對象的實例,那麼我們就可以使用單例模式去創建實例。單例顧名思義就是隻有一個實例,對於單例模式我們通常又有兩種單例模式,分別是餓漢模式和懶漢模式。
1、餓漢單例:在類初始化的時候就加載
//餓漢單例模式
public class Singleton1 {
 // 定義一個單例類的實例對象instance,同時實例初始化,一定要用static修飾
 private static Singleton1 instance = new Singleton1();

 // 構造器是私有的,只能在該類中被使用,注意使用private修飾
 private Singleton1() {
 }

 // 對外提供一個訪問獲取該對象實例的方法,返回之前已經創建的實例
 public static Singleton1 getInstance() {
  return instance;
 }
}

2、懶漢單例:只有在第一次用到的時候纔會去加載
//懶漢單例模式
public class Singleton2 {
 // 定義一個單例類的實例對象instance,但是暫時不創建該對象的實例,一定要用static修飾
 private static Singleton2 instance = null;

 // 構造器是私有的,只能在該類中被使用,注意使用private修飾
 private Singleton2() {
 }

 // 對外提供一個訪問獲取該對象實例的方法,返回上面定義的實例對象
 public static Singleton2 getInstance() {
  // 因爲上面的實例對象在類初始化的時候並沒有創建,所以在此處判斷如果還沒有實例化,就創建一個實例
  if (instance == null) {
   instance = new Singleton2();
  }
  // 返回實例的時候,一定是不爲空的
  return instance;
 }
}
餓漢模式是線程安全的,而懶漢模式因爲在用到的時候纔去加載,所以可能在多線程中同事請求創建實例,所以這就導致懶漢模式加載是線程不安全的。因此我們一般在使用懶漢模式的時候,會加入多線程控制,以多線程下也是正常的單例模式。代碼寫法一般有兩種,一種是在方法上加入synchronized,另一種是將創建代碼放在synchronized包裹的代碼塊中:
// 加入synchronized目的是防止多線程下創建多個實例,那樣其實就不是單例了
public static synchronized Singleton2 getInstance() {
 if (instance == null) {
  instance = new Singleton2();
 }
 return instance;
}
對於上面兩種單例模式都有各自的缺陷,惡漢模式沒有延遲加載,導致的結果可能是在初始化的時候,消耗太多資源。懶漢模式雖然延遲加載了,但是爲了線程安全導致效率降低。

3、餓漢改版延遲加載實例:使用靜態內部類延遲加載
//內部類獲得餓漢單例模式
public class Singleton3 {
 // 定義一個內部類,該內部類持有一個外部類的實例對象
 private static class SingletonHolder {
  private final static Singleton3 INSTANCE = new Singleton3();
 }

 // 構造器是私有的,只能在該類中被使用,注意使用private修飾
 private Singleton3() {
 }

 // 對外提供一個訪問獲取該對象實例的方法
 public static Singleton3 getInstance() {
  // 通過內部類對象獲取單例實例對象
  Singleton3 instance = SingletonHolder.INSTANCE;
  return instance;
 }
}
這樣做的目的是在類初始化的時候並不加載內部類對象,因爲內部類沒有被使用所以是不會加載的。當獲取單例實例的時候,內部類會去實例化一個單例對象,然後返回給調運放,這樣既保證了線程安全,也保證了延遲加載。如果在不要求延遲加載的情況下,建議使用第一種餓漢模式,如果在要求延遲加載的情況下,建議使用第三種以保證效率。

    單例模式還有兩種創建方式,一種是通過枚舉,一種是通過volatile關鍵字雙重校驗,不過個人覺得很少有人用這兩種方式。

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