單例模式(Singleton)的 5 種實現(xiàn)方式及最佳實踐

單例模式是最常用的創(chuàng)建型設計模式之一,核心目標是保證一個類在整個應用生命周期內(nèi)只有一個實例,并提供全局唯一的訪問入口。本文梳理了單例模式的 5 種經(jīng)典實現(xiàn)方式,分析各方案的優(yōu)缺點及適用場景,同時給出工業(yè)級最佳實踐。

一、懶漢式單例模式(加鎖版,基礎版不加鎖,線程不安全)

核心特點:延遲初始化(第一次調(diào)用時創(chuàng)建實例),通過方法同步保證線程安全,但性能損耗大。

public class Singleton {
    // 私有靜態(tài)實例,初始為null(延遲加載)
    private static Singleton instance;
    
    // 私有構(gòu)造方法:禁止外部new創(chuàng)建實例
    private Singleton() {}
    
    // 同步方法:保證多線程下唯一實例
    public static synchronized Singleton getInstance() {
        if (instance == null) { // 懶加載:僅在首次調(diào)用時創(chuàng)建
            instance = new Singleton();
        }
        return instance;
    }
}

優(yōu)缺點

  • ? 優(yōu)點:完全延遲加載,無資源浪費;線程安全;實現(xiàn)簡單。
  • ? 缺點:synchronized 加在方法上,每次調(diào)用 getInstance() 都會加鎖,即使實例已創(chuàng)建,高并發(fā)場景下性能差。

適用場景
并發(fā)量極低、對性能要求不高的簡單場景(幾乎不推薦在生產(chǎn)環(huán)境使用)。

二、餓漢式單例模式

核心特點:類加載時立即初始化實例,天然線程安全,但可能造成資源浪費。

public class Singleton {
    // 類加載階段(初始化階段)就創(chuàng)建實例,JVM保證線程安全
    private static final Singleton instance = new Singleton();
    
    // 私有構(gòu)造方法
    private Singleton() {}
    
    // 無鎖獲取實例,性能極高
    public static Singleton getInstance() {
        return instance;
    }
}

優(yōu)缺點

  • ? 優(yōu)點:線程安全(JVM 類加載機制保證);無鎖開銷,性能最優(yōu);實現(xiàn)簡單。
  • ? 缺點:非延遲加載,若實例創(chuàng)建依賴重資源(如數(shù)據(jù)庫連接、大對象),且長期未使用,會造成資源浪費。

適用場景
實例創(chuàng)建成本低、啟動后大概率會被使用的場景(如工具類、輕量級配置類)。

三、雙重檢查鎖(DCL)單例模式

核心特點:兼顧延遲加載和線程安全,僅在實例未創(chuàng)建時加鎖,性能接近餓漢式。需通過 volatile 解決指令重排問題。

public class Singleton {
    // volatile關鍵字:1.保證可見性 2.禁止指令重排(關鍵?。?    private static volatile Singleton instance;
    
    // 私有構(gòu)造方法
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) { // 第一次檢查:實例已創(chuàng)建則直接返回,避免加鎖
            synchronized (Singleton.class) { // 類對象鎖:僅當實例未創(chuàng)建時加鎖
                if (instance == null) { // 第二次檢查:防止多線程并發(fā)創(chuàng)建
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

關鍵解釋
instance = new Singleton() 并非原子操作,JVM 會拆分為 3 步:
1、分配內(nèi)存空間;
2、初始化實例;
3、將 instance 指向內(nèi)存地址。
若不加 volatile,JVM 可能指令重排為 1→3→2,導致其他線程在第二步完成前,看到 instance 不為 null 但實例未初始化,引發(fā)空指針異常。

優(yōu)缺點

  • ? 優(yōu)點:延遲加載;僅首次創(chuàng)建時加鎖,高并發(fā)性能優(yōu)異;線程安全。
  • ? 缺點:實現(xiàn)稍復雜,易因遺漏 volatile 導致線程安全問題。

適用場景
高并發(fā)場景、實例創(chuàng)建成本高且需延遲加載的場景(生產(chǎn)環(huán)境常用)。

四、靜態(tài)內(nèi)部類單例模式(推薦)

核心特點:利用 JVM 類加載機制實現(xiàn)延遲加載 + 線程安全,兼顧性能與優(yōu)雅性,是最推薦的非枚舉實現(xiàn)方式。

public class Singleton {
    // 私有構(gòu)造方法
    private Singleton() {}
    
    // 靜態(tài)內(nèi)部類:獨立于外部類,僅在被調(diào)用時加載
    private static class InnerClass {
        // JVM保證靜態(tài)變量初始化的線程安全
        private static final Singleton instance = new Singleton();
    }
    
    // 外部類調(diào)用時,才觸發(fā)內(nèi)部類加載和實例創(chuàng)建
    public static Singleton getInstance() {
        return InnerClass.instance;
    }
}

關鍵解釋

  • 外部類 Singleton 加載時,靜態(tài)內(nèi)部類 InnerClass 不會被加載,實現(xiàn)延遲加載;
  • 調(diào)用 getInstance() 時,InnerClass 被 JVM 加載,其靜態(tài)變量 instance 由 JVM 保證線程安全地初始化;
  • 靜態(tài)內(nèi)部類的加載是線程安全的,無需加鎖。

優(yōu)缺點

  • ? 優(yōu)點:延遲加載;線程安全;無鎖開銷,性能優(yōu);實現(xiàn)優(yōu)雅,無指令重排風險。
  • ? 缺點:無法通過反射完全防止實例被創(chuàng)建(可通過構(gòu)造方法加校驗規(guī)避)。

適用場景
絕大多數(shù)生產(chǎn)環(huán)境場景(平衡性能、優(yōu)雅性、安全性的最優(yōu)解)。

五、枚舉單例模式(最佳實踐)

核心特點:利用枚舉的天然特性實現(xiàn)單例,防反射、防序列化破壞,是《Effective Java》推薦的最優(yōu)方案。

// 枚舉類天然單例,由JVM完全保證唯一性
public enum Singleton {
    INSTANCE; // 唯一實例,JVM加載時初始化
    
    // 單例的業(yè)務方法
    public void doSomething() {
        System.out.println("枚舉單例執(zhí)行業(yè)務邏輯");
    }
    
    // 可選:添加自定義屬性/初始化邏輯
    private String config;
    // 枚舉的構(gòu)造方法默認私有,無需顯式聲明
    Singleton() {
        // 模擬初始化邏輯(如加載配置)
        this.config = "default-config";
    }
    
    public String getConfig() {
        return config;
    }
}

// 使用方式(全局唯一)
public class Client {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        System.out.println(instance1 == instance2); // true
        
        instance1.doSomething(); // 調(diào)用業(yè)務方法
    }
}

關鍵解釋

  • 枚舉的實例由 JVM 在類加載時創(chuàng)建,天然線程安全;
  • 反射無法創(chuàng)建枚舉實例(Constructor.newInstance() 會拋出 IllegalArgumentException);
  • 序列化時,枚舉的 readResolve() 方法被 JVM 重寫,保證反序列化后仍是原實例。

優(yōu)缺點

  • ? 優(yōu)點:絕對線程安全;防反射 / 序列化破壞;實現(xiàn)極簡;無需額外處理。
  • ? 缺點:非延遲加載(枚舉類加載時即初始化);部分開發(fā)者對枚舉單例的認知不足。

適用場景
對單例唯一性要求極高的場景(如分布式系統(tǒng)、安全框架、核心配置類),是工業(yè)級最佳實踐。

六、擴展:Spring 中的單例實現(xiàn)

Spring 容器中的 @Scope("singleton")(默認作用域)并非直接使用上述代碼,而是通過容器管理實現(xiàn)單例:
1、Spring Bean 默認在容器初始化時創(chuàng)建(類似餓漢式);
2、通過 @Lazy 注解可實現(xiàn)延遲加載(類似懶漢式);
3、Spring 容器保證單例的線程安全,底層結(jié)合了雙重檢查鎖 + 容器緩存機制。

七、總結(jié):選型指南

image.png

核心原則:
1、簡單場景用餓漢式,需延遲加載用靜態(tài)內(nèi)部類;
2、對安全性要求極致時,優(yōu)先選擇枚舉單例;
3、避免手動實現(xiàn)復雜的單例邏輯,優(yōu)先利用框架(如 Spring)的單例管理能力。

最終目的:
保證核心對象全局唯一,減少資源消耗;

SpringBoot 核心應用場景:
Spring 容器中默認所有 Bean 為單例(singleton)、ApplicationContext實例全局唯一、核心工具類(如Environment)單例

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容