前言
單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。
這種模式涉及到一個單一的類,該類負責建立自己的物件,同時確保只有單個物件被建立。這個類提供了一種訪問其唯一的物件的方式,可以直接訪問,不需要例項化該類的物件。
餓漢單例
是否多執行緒安全:是
是否懶載入:否
正如名字含義,餓漢需要直接建立例項。
public class EhSingleton {
private static EhSingleton ehSingleton = new EhSingleton();
private EhSingleton() {}
public static EhSingleton getInstance(){
return ehSingleton;
}
}
缺點: 類載入就初始化,浪費記憶體
優點: 沒有加鎖,執行效率高。還是執行緒安全的例項。
懶漢單例
懶漢單例,在類初始化不會建立例項,只有被呼叫時才會建立例項。
非執行緒安全的懶漢單例
是否多執行緒安全:否
是否懶載入: 是
public class LazySingleton {
private static LazySingleton ehSingleton;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (ehSingleton == null) {
ehSingleton = new LazySingleton();
}
return ehSingleton;
}
}
例項在呼叫 getInstance
才會建立例項,這樣的優點是不佔記憶體,在單執行緒模式下,是安全的。但是多執行緒模式下,多個執行緒同時執行 if (ehSingleton == null)
結果都為 true
,會建立多個例項,所以上面的懶漢單例是一個執行緒不安全的例項。
加同步鎖的懶漢單例
是否多執行緒安全:是
是否懶載入: 是
為了解決多個執行緒同時執行 if (ehSingleton == null)
的問題,getInstance
方法新增同步鎖,這樣就保證了一個執行緒進入了 getInstance
方法,別的執行緒就無法進入該方法,只有執行完畢之後,其他執行緒才能進入該方法,同一時間只有一個執行緒才能進入該方法。
public class LazySingletonSync {
private static LazySingletonSync lazySingletonSync;
private LazySingletonSync() {}
public static synchronized LazySingletonSync getInstance() {
if (lazySingletonSync == null) {
lazySingletonSync =new LazySingletonSync();
}
return lazySingletonSync;
}
}
這樣配置雖然保證了執行緒的安全性,但是效率低,只有在第一次呼叫初始化之後,才需要同步,初始化之後都不需要進行同步。鎖的粒度太大,影響了程式的執行效率。
雙重檢驗懶漢單例
是否多執行緒安全:是
是否懶載入:是
使用 synchronized
宣告的方法,在多個執行緒訪問,比如A執行緒訪問時,其他執行緒必須等待A執行緒執行完畢之後才能訪問,大大的降低的程式的執行效率。這個時候使用 synchronized
程式碼塊優化執行時間,減少鎖的粒度。
雙重檢驗首先判斷例項是否為空,然後使用 synchronized (LazySingletonDoubleCheck.class)
使用類鎖,鎖住整個類,執行完程式碼塊的程式碼之後,新建了例項,其他程式碼都不走 if (lazySingletonDoubleCheck == null)
裡面,只會在最開始的時候效率變慢。而 synchronized
裡面還需要判斷是因為可能同時有多個執行緒都執行到 synchronized (LazySingletonDoubleCheck.class)
,如果有一個執行緒執行緒新建例項,其他執行緒就能獲取到 lazySingletonDoubleCheck
不為空,就不會再建立例項了。
public class LazySingletonDoubleCheck {
private static LazySingletonDoubleCheck lazySingletonDoubleCheck;
private LazySingletonDoubleCheck() {}
public static LazySingletonDoubleCheck getInstance() {
if (lazySingletonDoubleCheck == null) {
synchronized (LazySingletonDoubleCheck.class) {
if (lazySingletonDoubleCheck == null) {
lazySingletonDoubleCheck = new LazySingletonDoubleCheck();
}
}
}
return lazySingletonDoubleCheck;
}
}
靜態內部類
是否多執行緒安全:是
是否懶載入:是
外部類載入時,並不會載入內部類,也就不會執行 new SingletonHolder()
,這屬於懶載入。只有第一次呼叫 getInstance()
方法時才會載入 SingletonHolder
類。而靜態內部類是執行緒安全的。
靜態內部類為什麼是執行緒安全
靜態內部類利用了類載入機制的初始化階段 getInstance()
方法時,虛擬機器才會載入 SingletonHolder
靜態內部類,
然後在載入靜態內部類,該內部類有靜態變數,JVM會改內部生成
虛擬機器會保證
這種方式不僅實現延遲載入,也保障執行緒安全。
public class StaticClass {
private StaticClass() {}
private static class SingletonHolder {
private static final SingletonHolder INSTANCE = new SingletonHolder();
}
public static final SingletonHolder getInstance() {
return SingletonHolder.INSTANCE;
}
}
總結
- 餓漢單例類載入就初始化,在沒有加鎖的情況下實現了執行緒安全,執行效率高。但是無論有沒有呼叫例項都會被建立,比較浪費記憶體。
- 為了解決記憶體的浪費,使用了懶漢單例,但是懶漢單例在多執行緒下會引發執行緒不安全的問題。
- 不安全的懶漢單例,使用
synchronized
宣告同步方法,獲取例項就是安全了。 synchronized
宣告方法每次執行緒呼叫方法,其它執行緒只能等待,降低了程式的執行效率。- 為了減少鎖的粒度,使用
synchronized
程式碼塊,因為只有少量的執行緒獲取例項,例項是null,建立例項之後,後續的執行緒都能獲取到執行緒,也就無需使用鎖了。可能多個執行緒執行到synchronized
,所以同步程式碼塊還需要再次判斷一次。 - 靜態內部類賦值實際是呼叫
方法,而虛擬機器保證 方法使用鎖,保證執行緒安全。