getInstance()public class HungrySingleton {
// 類載入時即建立實例,JVM 保證執行緒安全
private static final HungrySingleton INSTANCE = new HungrySingleton();
private HungrySingleton() {} // 私有建構子
public static HungrySingleton getInstance() {
return INSTANCE;
}
}
優點:無鎖、效能最高、實現最簡單、JVM 類載入機制天然保證安全。
缺點:無延遲載入,若實例建構耗時/資源佔用大且未必使用,會造成浪費。
public class LazySingletonDCL {
private static volatile LazySingletonDCL instance; // volatile 關鍵!
private LazySingletonDCL() {}
public static LazySingletonDCL getInstance() {
if (instance == null) { // 第一次檢查(無鎖)
synchronized (LazySingletonDCL.class) { // 同步鎖
if (instance == null) { // 第二次檢查
instance = new LazySingletonDCL();
}
}
}
return instance;
}
}
關鍵點:volatile 禁止指令重排,防止半初始化物件被其他執行緒見到。
缺點:程式碼較繁瑣,效能較餓漢式稍差(首次需同步)。
public class LazySingletonHolder {
private LazySingletonHolder() {}
// 只有呼叫 getInstance() 時才會載入 SingletonHolder 類
private static class SingletonHolder {
static final LazySingletonHolder INSTANCE = new LazySingletonHolder();
}
public static LazySingletonHolder getInstance() {
return SingletonHolder.INSTANCE;
}
}
getInstance() 觸發 SingletonHolder 初始化volatile、無需同步塊、零額外開銷public class SingletonKerriganF {
private static String arg = null;
private static class SingletonHolder {
static final SingletonKerriganF INSTANCE = new SingletonKerriganF();
}
public static SingletonKerriganF getInstance() {
return SingletonHolder.INSTANCE;
}
private SingletonKerriganF() {
System.out.println("Kerrigan get arg: " + getArg()); // 印出 null
System.out.println("Kerrigan created!");
}
public static String getArg() { return arg; }
public static void setArg(String arg) { SingletonKerriganF.arg = arg; }
public static void main(String[] args) {
System.out.println("外部類載入完成,內部類尚未載入");
setArg("config from file"); // 設定配置
getInstance(); // 此時才觸發內部類載入與實例建構
}
}
輸出結果:
外部類載入完成,內部類尚未載入
Kerrigan get arg: config from file // 配置已生效!
Kerrigan created!
證明:內部類載入發生在 getInstance() 呼叫時,而非外部類載入時,完美支援「先配置、後建構」場景。
public enum EnumSingleton {
INSTANCE;
public void doSomething() { /* 業務邏輯 */ }
}
優點:防序列化破壞、防反射攻擊、語法最簡潔。缺點:無延遲載入、無法繼承。
| 場景 | 推薦寫法 | 理由 |
|---|---|---|
| 一般業務單例、資源佔用小 | 餓漢式 | 最簡單、效能最好、零 Bug 風險 |
| 建構耗時大、資源佔用高、可能不使用 | 靜態內部類 | 延遲載入 + 執行緒安全 + 無鎖讀取 |
| 需防序列化/反射破壞、或偏好極簡語法 | 枚舉單例 | 語言層面保證單一性 |
| 需懶載入且 JDK < 1.5(無 volatile 語意) | 同步方法 | 舊環境相容,效能差 |
現代 Java 開發中,餓漢式與靜態內部類覆蓋 95%+ 場景。理解 JVM 類載入機制與 volatile 語意,是寫出正確高效單例的關鍵。切記:雙重檢查鎖若漏掉 volatile 即為錯誤寫法。