「核心設計」
把建構函式設定為私有,不允許外部建立類的例項,透過靜態方法getInstance()
獲取唯一例項。
1. 餓漢式
設計類的私有靜態例項instance
,在類載入的時候,instance
就已經建立並初始化好了,所以,instance
例項的建立過程是執行緒安全的。
透過靜態方法getInstance()
返回instance
時,不存在競態條件,也是執行緒安全的。
優點:簡單,執行緒安全,效能高
缺點:不支援延遲載入,如果例項佔用資源多或初始化耗時長,提前初始化例項是資源
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static final IdGenerator instance = new IdGenerator();
private IdGenerator() {}
public static IdGenerator getInstance() {
return instance;
}
public long getId() {
return id.incrementAndGet();
}
}
2. 懶漢式
不事先初始化instance
,在getInstance()
方法中增加一步,判斷如果instance==null
,則先建立例項。
優點:簡單,支援懶載入
缺點:為保證執行緒安全,getInstance()
需要上鎖同步,併發度低,當單例被頻繁用到時,會成為效能瓶頸
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static IdGenerator instance;
private IdGenerator() {}
public static synchronized IdGenerator getInstance() {
if (instance == null) {
instance = new IdGenerator();
}
return instance;
}
public long getId() {
return id.incrementAndGet();
}
}
3. 雙重檢測
為提高懶漢式的併發能力,減小鎖的顆粒度。
優點:既支援懶載入,又支援高併發
缺點:程式碼邏輯複雜,容易寫錯
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static IdGenerator instance;
private IdGenerator() {}
public static IdGenerator getInstance() {
if (instance == null) {
synchronized(IdGenerator.class) { // 此處為類級別的鎖
if (instance == null) {
instance = new IdGenerator();
}
}
}
return instance;
}
public long getId() {
return id.incrementAndGet();
}
}
說明:
- 同步塊內外兩次檢查
instance==null
,是為了防止出現併發時,幾個執行緒相繼獲得鎖進入同步塊後,分別建立instance例項,多次賦值 - 本程式碼在多執行緒條件下存在競態條件,需要考慮
instance
變數的可見性和賦值的原子性,故程式碼邏輯複雜,易出錯:instance
要對全部執行緒可見,這個透過synchronized
或volatile
關鍵字保證,本程式碼基於前者- 賦值的原子性,即
instance = new IdGenerator()
這一行,在舊版本的java(java7及以下),本語句編譯後不是原子的,若發生指令重排,其他執行緒可能拿到錯誤的instance
物件
- 基於第2-2點,舊版本的java中,為保證併發時的正確性,
instance
的宣告語句需要volatile
關鍵字避免指令重排,即要改為private volatile static IdGenerator instance;
4. 使用靜態內部類
使用靜態內部類,能達到“雙重檢測”一樣的效果,且實現更簡單,不易出錯。
優點:高併發、執行緒安全、支援懶載入、簡單
缺點:暫無
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private IdGenerator() {}
private static class SingletonHolder{
private static final IdGenerator instance = new IdGenerator();
}
public static IdGenerator getInstance() {
return SingletonHolder.instance;
}
public long getId() {
return id.incrementAndGet();
}
}
說明:
SingletonHolder
是一個靜態內部類,當外部類IdGenerator
被載入的時候,並不會建立SingletonHolder
例項物件;只有當呼叫getInstance()
方法時,SingletonHolder
才會被載入,這個時候才會建立instance
- instance 的唯一性、建立過程的執行緒安全性,都由 JVM 來保證
- 現在若使用Intelij Idea 寫單例,寫一個“雙重檢測”的單例實現後,idea會自動提示重構為“使用內部靜態類”的方式
5. 其他方法
對於Java語言而言,還可以透過列舉型別實現單例,它是透過 Java 列舉型別本身的特性,保證了例項建立的執行緒安全性和例項的唯一性:
public enum IdGenerator {
INSTANCE;
private AtomicLong id = new AtomicLong(0);
public long getId() {
return id.incrementAndGet();
}
}
What’s More
本文程式碼,直接使用了極客時間王爭老師的專欄《設計模式之美》,點選的『這裡』可查閱,也可以掃下面二維碼訂閱。
注:轉載本文,請與Gevin聯絡
歡迎關注我的微信公眾賬號