天天看點

Java設計模式:單例模式(Singleton Pattern)

單例模式定義

單例模式確定一個類隻有一個執行個體,并提供一個全局通路點。

經典的單例模式模型

// NOTE: This is not thread safe!

public class Singleton {
  private static Singleton uniqueInstance;
 
  // other useful instance variables here
 
  private Singleton() {}
 
  public static Singleton getInstance() {
    if (uniqueInstance == null) {
      uniqueInstance = new Singleton();
    }
    return uniqueInstance;
  }
 
  // other useful methods here
}      

1)私有的構造函數確定了外部無法建立該類。

2)利用一個靜态私有的自身類變量來儲存自身的唯一執行個體。

3)公有的靜态方法提供了入口,用類名可以直接調用

4)首次調用getIntance()時,會建立該類的執行個體并儲存該執行個體

5)再次調用getIntance()時,不會建立該類的執行個體,直接調用儲存的執行個體

3)4)5)點優點:靜态通路(類似全局變量),延遲執行個體化;缺點:線程不安全

當使用多線程時,如果多個線程同時走到

if (uniqueInstance == null) {
    uniqueInstance = new Singleton();
}      

就會同時建立多個執行個體。

那怎麼辦呢?

方法1)

隻要把getInstance() 這個全局通路點改成同步即可

public static synchronized Singleton getInstance() {
  if (uniqueInstance == null) {
    uniqueInstance = new Singleton();
  }
  return uniqueInstance;
}      

synchronized 關鍵字可以迫使每個線程進入到這個方法之前,都要先等候其它線程離開該方法,也就是說不會有兩個線程可以同時進入到這個方法。

但問題來了:

隻有當執行個體變量沒有建立之前這個同步是真正需要的,當執行個體被建立出來之後,這個同步就變的沒有意義了;

你要知道,同步會使用程式效率降低。

如果getInstance()不是太頻繁,它的性能對程式不是很關鍵,可以使用這個方法不用理會。

但如果getInstance()太頻繁,你就要優化了。

是以

解決方案:

方法2)立即建立執行個體,而不是延遲執行個體化

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

這種做法使JVM在加載這個類時馬上建立此類的唯一的單例執行個體。這樣保證在任何線程通路執行個體變量時,執行個體變量一定先建立了。

方法3)雙重檢查加鎖(double-checked locking),減少 getInstance()中使用同步

要點:檢查執行個體是否已建立,如果未建立,才同步

public class Singleton {
  private volatile static Singleton uniqueInstance;
 
  private Singleton() {}
 
  public static Singleton getInstance() {
    if (uniqueInstance == null) { //檢查執行個體,如果不存在,就進入同步區域
      synchronized (Singleton.class) {  // 隻有第一次才會執行這裡的同步區域
        if (uniqueInstance == null) {  // 進入區域後,再檢查一次,如果仍是null,才建立執行個體
          uniqueInstance = new Singleton();
        }
      }
    }
    return uniqueInstance;
  }
}      

volatile 關鍵字可以確定變量被真正讀取或儲存。

這個做法可以幫助我們大大減少getInstance()時間上的耗費。

3種方法的适用性

方法1):保證可行的最直接做法,沒有對性能上的任何考慮

方法2):靜态初始化執行個體,直接先執行個體化一個執行個體(看似先浪費了記憶體),但這個執行個體我們一定會建立,也不是不能接受

方法3):確定使用Java5以上版本。