單例模式是面試必考、開發常用的創建型設計模式,核心要求:全域唯一實例、延遲初始化、執行緒安全、防序列化破壞、防反射攻擊。本文詳細剖析五種經典實現方式的原理、優缺點與適用場景,並給出現代 Java 最佳實踐建議。
public class Singleton {
// 1. 私有構造器,防止外部 new
private Singleton() {}
// 2. 類載入時即創建實例 (JVM 保證執行緒安全)
private static final Singleton INSTANCE = new Singleton();
// 3. 公開靜態獲取方法
public static Singleton getInstance() {
return INSTANCE;
}
}
| 維度 | 評價 |
|---|---|
| 執行緒安全 | ⭐⭐⭐⭐⭐ (JVM 類載入機制天然保證) |
| 延遲加載 | ❌ 類載入即創建,可能造成資源浪費 |
| 程式碼複雜度 | ⭐⭐⭐⭐⭐ (最簡潔) |
| 防反射/序列化 | ❌ 需額外處理 |
| 適用場景 | 類輕量、一定會被使用、無外部依賴 |
public class Singleton {
private Singleton() {}
private static Singleton instance = null;
// synchronized 鎖住整個方法,效能極低
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
| 維度 | 評價 |
|---|---|
| 執行緒安全 | ⭐⭐⭐⭐⭐ |
| 延遲加載 | ✅ 首次調用才創建 |
| 效能 | ⭐ (每次調用都同步,實際只需首次) |
| 適用場景 | 極少調用、教學演示 |
public class Singleton {
private Singleton() {}
// volatile 關鍵:禁止指令重排序,保證可見性
private static volatile Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) { // 第一次檢查 (無鎖,高效)
synchronized (Singleton.class) { // 鎖類對象
if (instance == null) { // 第二次檢查 (鎖內,安全)
instance = new Singleton(); // 非原子操作:分配記憶體→初始化→賦值引用
}
}
}
return instance;
}
}
關鍵點解析:
volatile:防止 instance = new Singleton() 指令重排序 (分配記憶體 → 賦值引用 → 初始化對象),避免其他執行緒看到半初始化對象| 維度 | 評價 |
|---|---|
| 執行緒安全 | ⭐⭐⭐⭐ (需正確加 volatile) |
| 延遲加載 | ✅ |
| 效能 | ⭐⭐⭐⭐ (首次同步,後續無鎖) |
| 程式碼複雜度 | ⭐⭐ (易漏 volatile、易寫錯鎖對象) |
public class Singleton {
private Singleton() {}
// 靜態內部類,只有在調用 getInstance() 時才會被載入初始化
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
原理:利用 JVM 類載入機制 — 外部類載入時不載入內部類,首次調用 getInstance() 觸發 Holder 類初始化,JVM 保證執行緒安全。
| 維度 | 評價 |
|---|---|
| 執行緒安全 | ⭐⭐⭐⭐⭐ (JVM 天然保證) |
| 延遲加載 | ✅ (首次調用才載入內部類) |
| 效能 | ⭐⭐⭐⭐⭐ (無同步開銷) |
| 程式碼複雜度 | ⭐⭐⭐⭐ (清晰易懂) |
| 防反射/序列化 | ❌ 需額外處理 |
| 推薦指數 | ⭐⭐⭐⭐⭐ 生產環境首選 |
public enum Singleton {
INSTANCE;
// 可添加實例欄位、方法
private String data;
public void doSomething() { System.out.println("Working..."); }
public String getData() { return data; }
public void setData(String data) { this.data = data; }
}
// 使用方式
Singleton.INSTANCE.doSomething();
為什麼枚舉最強?
Constructor.newInstance() 對枚舉類型拋出 IllegalArgumentExceptionvalueOf 還原,不走構造器| 維度 | 評價 |
|---|---|
| 執行緒安全 | ⭐⭐⭐⭐⭐ |
| 延遲加載 | ✅ (首次引用枚舉常量才初始化) |
| 防反射 | ⭐⭐⭐⭐⭐ (語言級保證) |
| 防序列化 | ⭐⭐⭐⭐⭐ (語言級保證) |
| 缺點 | 不支援延遲初始化邏輯 (如需配置參數)、不支援繼承 |
| 寫法 | 執行緒安全 | 延遲加載 | 效能 | 防反射 | 防序列化 | 推薦場景 |
|---|---|---|---|---|---|---|
| 餓漢式 | ✅ | ❌ | 極高 | ❌ | ❌ | 輕量、必用、無依賴 |
| 同步懶漢 | ✅ | ✅ | 極低 | ❌ | ❌ | 教學、極少用 |
| DCL | ✅ (需 volatile) | ✅ | 高 | ❌ | ❌ | 舊版專案維護 |
| 靜態內部類 | ✅ | ✅ | 極高 | ❌ | ❌ | 一般生產首選 |
| 枚舉 | ✅ | ✅ | 極高 | ✅ | ✅ | 最高安全要求 |
// 防反射:構造器中檢查
private Singleton() {
if (INSTANCE != null) { // 或 Holder.INSTANCE
throw new IllegalStateException("已存在實例,禁止反射創建");
}
}
// 防序列化:實現 readResolve()
private Object readResolve() {
return INSTANCE; // 返回現有實例,忽略反序列化產生的新對象
}
ApplicationContext 容器範圍內唯一 (預設 scope=singleton)現代 Java 最佳實踐:
- 一般業務:靜態內部類 — 簡潔、安全、延遲、高效
- 安全敏感/框架核心:枚舉 — 終極防禦
- Spring 專案:交給 Spring 管理 —
@Component+@Scope("singleton"),專注業務邏輯
單例模式看似簡單,實則蘊含 Java 記憶體模型、類載入機制、反射序列化機制等核心知識。深度理解五種寫法演進邏輯,不僅能應對面試,更能在架構設計中做出正確選擇。