餓漢式
餓漢式:類載入就會導致該單例項物件被建立
// 問題1:為什麼加 final
// 問題2:如果實現了序列化介面, 還要做什麼來防止反序列化破壞單例
public final class Singleton_hungry implements Serializable {
// 問題3:為什麼設定為私有? 是否能防止反射建立新的例項?
private Singleton_hungry(){}
// 問題4:這樣初始化是否能保證單例物件建立時的執行緒安全?
private static Singleton_hungry INSTANCE = new Singleton_hungry();
// 問題5:為什麼提供靜態方法而不是直接將 INSTANCE 設定為 public, 說出你知道的理由
public static Singleton_hungry getInstance() {
return INSTANCE;
}
public Object readResolve(){ // 防止反射建立新的例項?
return INSTANCE;
}
}
-
問題1:
避免子類覆蓋父類的一些方法,導致執行緒不安全。 -
問題2:
實現readResolve
方法。當從物件流ObjectInputStream
中讀取物件時,會檢查物件的類否定義了readResolve
方法。如果定義了,則呼叫它返回我們想指定的物件(這裡就指定了返回單例物件)。 -
問題3:防止通過
new
建立物件例項。不能防止反射建立新的例項。 -
問題4:可以。靜態變數初始化在類載入時進行,由
jvm
進行管理,可以保證執行緒安全。 -
問題5:通過方法,可以提高擴充性,改進餓漢式轉化為懶漢式、利用泛型特性、增加對單例物件的控制操作。
列舉單例
enum Singleton {
INSTANCE;
}
-
問題1:列舉單例是如何限制例項個數的
單例相當於列舉的靜態成員變數,定義幾個就有幾個例項。 -
問題2:列舉單例在建立時是否有併發問題
單例相當於列舉的靜態成員變數,類載入時初始化,由jvm
進行管理,可以保證執行緒安全。 -
問題3:列舉單例能否被反射破壞單例
不能 -
問題4:列舉單例能否被反序列化破壞單例
列舉實現了Serializable
介面,可序列化,但不會被反序列破壞單例。 -
問題5:列舉單例屬於懶漢式還是餓漢式
餓漢式 -
問題6:列舉單例如果希望加入一些單例建立時的初始化邏輯該如何做
列舉允許構造方法
懶漢式
public final class Singleton_lazy {
private Singleton_lazy(){}
private static Singleton_lazy INSTANCE = null;
// 缺點
public static synchronized Singleton_lazy getInstance() {
if(INSTANCE != null) {
return INSTANCE;
}
INSTANCE = new Singleton_lazy();
return INSTANCE;
}
}
synchronized
保證執行緒安全,但鎖粒度較大,效能低。
DCL 懶漢式
public final class Singleton_DCL {
private Singleton_DCL() {}
// 問題1:解釋為什麼要加 volatile ?
private static volatile Singleton_DCL INSTANCE= null;
// 問題2:對比實現3, 說出這樣做的意義
public static Singleton_DCL getInstance() {
if(INSTANCE != null) {
return INSTANCE;
}
synchronized (Singleton_DCL.class) {
// 問題3:為什麼還要在這裡加為空判斷, 之前不是判斷過了嗎
if(INSTANCE != null) {
return INSTANCE;
}
INSTANCE = new Singleton_DCL();
return INSTANCE;
}
}
}
-
問題1:避免指令重排序,導致賦值語句先於建構函式執行,得到一個未初始化完畢的物件。
-
問題2、3:
Double Check Lock
機制。同步程式碼塊外部的判斷語句主要用於INSTANCE
初始化並賦值之後,此時INSTANCE != null
,如果有多個執行緒嘗試獲取單例,可以提前返回,不用執行同步程式碼塊。而同步程式碼塊內部的判斷主要用於第一次初始化時,INSTANCE = null
,此時可以有多個執行緒嘗試獲取INSTANCE
,只能有一個執行緒進入同步程式碼塊,其他執行緒在同步程式碼塊外阻塞,該執行緒建立一個單例物件之後,喚醒其他執行緒,再進入同步程式碼塊,發現INSTANCE != null
,則直接返回,不用重新建立單例物件,提高了效率。
靜態內部類懶漢單例
public final class Singleton_LazyHolder {
private Singleton_LazyHolder(){}
// 問題1:屬於懶漢式還是餓漢式
private static class LazyHolder{
static final Singleton_LazyHolder INSTANCE = new Singleton_LazyHolder();
}
// 問題2:在建立時是否有併發問題
public static Singleton_LazyHolder getInstance() {
return LazyHolder.INSTANCE;
}
}
-
問題1:懶漢式。靜態內部類只有在被方法呼叫的時候才進行初始化,類載入。
-
問題2:無,類載入由
jvm
進行,執行緒安全。