6種單例模式實現
普通懶漢式
public class Singleton {
/** 單例物件 */
private static Singleton instance;
/**
* 私有構造方法.
*/
private Singleton() {
}
/**
* 靜態方法, 用於獲取單利物件.
* 如果單例物件未建立, 則建立新單例物件, 否則直接返回該物件.
*
* @return 單例物件.
*/
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
最簡單的懶漢式單例,在首次呼叫 getInstance(); 時,會對單例物件進行例項化。
然而,這種方式明顯無法在多執行緒模式下正常工作。當執行緒併發呼叫getInstance(); 時,由於執行緒之間沒有進行同步,有可能兩個執行緒同時進入 if 條件,導致例項化兩次。
執行緒安全的懶漢式
public class Singleton {
/** 單例物件 */
private static Singleton instance;
/**
* 私有構造方法.
*/
private Singleton() {
}
/**
* 靜態方法, 用於獲取單利物件.
* 如果單例物件未建立, 則建立新單例物件, 否則直接返回該物件.
*
* @return 單例物件.
*/
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
最簡單的執行緒安全的懶漢模式,通過在 getInstance() 方法上新增 synchronized 關鍵字,保證同一時間僅有一個執行緒能夠執行該程式碼段,以保證不會出現上面一種方法產生的問題。
然而,這種方法效率很低。每次呼叫 getInstance() 方法,都將為程式碼段加鎖,同一時間該程式碼段只能被一個執行緒訪問。然而除了首次呼叫外,都是不需要同步的,因為 instance 已經被例項化。
Double-Check
public class Singleton {
/** 單例物件 */
private static Singleton instance;
/**
* 私有構造方法.
*/
private Singleton() {
}
/**
* 靜態方法, 用於獲取單利物件.
* 如果單例物件未建立, 則建立新單例物件, 否則直接返回該物件.
*
* @return 單例物件.
*/
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Double-check 即雙重校驗,該方法是針對上述方法提出的一種改進方案。
在 getInstance() 方法中,通過不加鎖判斷 instance 是否例項化。如果沒有例項化,再進行加鎖、例項化過程,以減少在例項化後呼叫 getInstance() 方法導致的效能損耗。
缺陷:instance = new Singleton()語句,看起來是一句程式碼,但實際上不是一個原子操作。它大致做了三件事:
1)給Singleton的例項分配記憶體
2)呼叫建構函式,初始化成員欄位
3)將insance物件指向分配的記憶體空間
但是由於Java編譯器允許處理器亂序執行,以及各種其他情況,在JDK1.5前上面的第二步和第三步沒有辦法保證執行順序。當執行順序是1-3-2時,如果執行到3後還沒執行2.這時候有另一個執行緒呼叫了getInstance()方法,那麼它判斷到的instance不是null,它不需要經過同步程式碼塊,直接獲取到了這個錯誤的物件去做事去了。這就導致了錯誤出現。解決方法是private static Singleton instance;改為private static volatile Singleton instance;以及使用JDK1.5以上的版本。建議還是使用靜態內部類的方式更好,可以規避上述問題
餓漢式
public class Singleton {
/** 單例物件, 類裝載時進行例項化. */
private static final Singleton singleton = new Singleton();
/**
* 私有構造方法.
*/
private Singleton() {
}
/**
* 靜態方法, 用於獲取單利物件.
*
* @return 單例物件.
*/
public static Singleton getInstance() {
return singleton;
}
}
餓漢式單例的原理是 ClassLoader 裝載類是單執行緒,通過這種機制避免了執行緒同步問題。
這種方式雖然避免了執行緒同步問題,但卻有可能帶來效能問題。
無論該類是否被使用, ClassLoader 都有可能(也有可能被 ClassLoader 忽略)載入該類並例項化該單例物件。所以在基礎類庫場景下,這種方法會無故消耗更多的資源。
靜態內部類方式
public class Singleton {
/**
* 私有構造方法.
*/
private Singleton() {
}
/**
* 靜態方法, 用於獲取單利物件.
*
* @return 單例物件.
*/
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
/** 單例物件, 類裝載時進行例項化. */
private static final Singleton instance = new Singleton();
}
}
這種方法同樣利用了 ClassLoader 單執行緒裝載的方式,避免了執行緒同步問題。然而他和上面一種方法不同的地方在於, instance 物件只有在 SingletonHolder 類被裝載的時候才會被例項化。也就是說,只有當 getInstance() 方法呼叫時,才會被例項化,這樣就避免了上述的資源損耗。
列舉方式
public enum Singleton {
INSTANCE;
}