单例模式(Singleton)的 5 种实现方式及最佳实践

单例模式是最常用的创建型设计模式之一,核心目标是保证一个类在整个应用生命周期内只有一个实例,并提供全局唯一的访问入口。本文梳理了单例模式的 5 种经典实现方式,分析各方案的优缺点及适用场景,同时给出工业级最佳实践。

一、懒汉式单例模式(加锁版,基础版不加锁,线程不安全)

核心特点:延迟初始化(第一次调用时创建实例),通过方法同步保证线程安全,但性能损耗大。

public class Singleton {
    // 私有静态实例,初始为null(延迟加载)
    private static Singleton instance;
    
    // 私有构造方法:禁止外部new创建实例
    private Singleton() {}
    
    // 同步方法:保证多线程下唯一实例
    public static synchronized Singleton getInstance() {
        if (instance == null) { // 懒加载:仅在首次调用时创建
            instance = new Singleton();
        }
        return instance;
    }
}

优缺点

  • ✅ 优点:完全延迟加载,无资源浪费;线程安全;实现简单。
  • ❌ 缺点:synchronized 加在方法上,每次调用 getInstance() 都会加锁,即使实例已创建,高并发场景下性能差。

适用场景
并发量极低、对性能要求不高的简单场景(几乎不推荐在生产环境使用)。

二、饿汉式单例模式

核心特点:类加载时立即初始化实例,天然线程安全,但可能造成资源浪费。

public class Singleton {
    // 类加载阶段(初始化阶段)就创建实例,JVM保证线程安全
    private static final Singleton instance = new Singleton();
    
    // 私有构造方法
    private Singleton() {}
    
    // 无锁获取实例,性能极高
    public static Singleton getInstance() {
        return instance;
    }
}

优缺点

  • ✅ 优点:线程安全(JVM 类加载机制保证);无锁开销,性能最优;实现简单。
  • ❌ 缺点:非延迟加载,若实例创建依赖重资源(如数据库连接、大对象),且长期未使用,会造成资源浪费。

适用场景
实例创建成本低、启动后大概率会被使用的场景(如工具类、轻量级配置类)。

三、双重检查锁(DCL)单例模式

核心特点:兼顾延迟加载和线程安全,仅在实例未创建时加锁,性能接近饿汉式。需通过 volatile 解决指令重排问题。

public class Singleton {
    // volatile关键字:1.保证可见性 2.禁止指令重排(关键!)
    private static volatile Singleton instance;
    
    // 私有构造方法
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查:实例已创建则直接返回,避免加锁
            synchronized (Singleton.class) { // 类对象锁:仅当实例未创建时加锁
                if (instance == null) { // 第二次检查:防止多线程并发创建
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

关键解释
instance = new Singleton() 并非原子操作,JVM 会拆分为 3 步:
1、分配内存空间;
2、初始化实例;
3、将 instance 指向内存地址。
若不加 volatile,JVM 可能指令重排为 1→3→2,导致其他线程在第二步完成前,看到 instance 不为 null 但实例未初始化,引发空指针异常。

优缺点

  • ✅ 优点:延迟加载;仅首次创建时加锁,高并发性能优异;线程安全。
  • ❌ 缺点:实现稍复杂,易因遗漏 volatile 导致线程安全问题。

适用场景
高并发场景、实例创建成本高且需延迟加载的场景(生产环境常用)。

四、静态内部类单例模式(推荐)

核心特点:利用 JVM 类加载机制实现延迟加载 + 线程安全,兼顾性能与优雅性,是最推荐的非枚举实现方式。

public class Singleton {
    // 私有构造方法
    private Singleton() {}
    
    // 静态内部类:独立于外部类,仅在被调用时加载
    private static class InnerClass {
        // JVM保证静态变量初始化的线程安全
        private static final Singleton instance = new Singleton();
    }
    
    // 外部类调用时,才触发内部类加载和实例创建
    public static Singleton getInstance() {
        return InnerClass.instance;
    }
}

关键解释

  • 外部类 Singleton 加载时,静态内部类 InnerClass 不会被加载,实现延迟加载;
  • 调用 getInstance() 时,InnerClass 被 JVM 加载,其静态变量 instance 由 JVM 保证线程安全地初始化;
  • 静态内部类的加载是线程安全的,无需加锁。

优缺点

  • ✅ 优点:延迟加载;线程安全;无锁开销,性能优;实现优雅,无指令重排风险。
  • ❌ 缺点:无法通过反射完全防止实例被创建(可通过构造方法加校验规避)。

适用场景
绝大多数生产环境场景(平衡性能、优雅性、安全性的最优解)。

五、枚举单例模式(最佳实践)

核心特点:利用枚举的天然特性实现单例,防反射、防序列化破坏,是《Effective Java》推荐的最优方案。

// 枚举类天然单例,由JVM完全保证唯一性
public enum Singleton {
    INSTANCE; // 唯一实例,JVM加载时初始化
    
    // 单例的业务方法
    public void doSomething() {
        System.out.println("枚举单例执行业务逻辑");
    }
    
    // 可选:添加自定义属性/初始化逻辑
    private String config;
    // 枚举的构造方法默认私有,无需显式声明
    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(); // 调用业务方法
    }
}

关键解释

  • 枚举的实例由 JVM 在类加载时创建,天然线程安全;
  • 反射无法创建枚举实例(Constructor.newInstance() 会抛出 IllegalArgumentException);
  • 序列化时,枚举的 readResolve() 方法被 JVM 重写,保证反序列化后仍是原实例。

优缺点

  • ✅ 优点:绝对线程安全;防反射 / 序列化破坏;实现极简;无需额外处理。
  • ❌ 缺点:非延迟加载(枚举类加载时即初始化);部分开发者对枚举单例的认知不足。

适用场景
对单例唯一性要求极高的场景(如分布式系统、安全框架、核心配置类),是工业级最佳实践。

六、扩展:Spring 中的单例实现

Spring 容器中的 @Scope("singleton")(默认作用域)并非直接使用上述代码,而是通过容器管理实现单例:
1、Spring Bean 默认在容器初始化时创建(类似饿汉式);
2、通过 @Lazy 注解可实现延迟加载(类似懒汉式);
3、Spring 容器保证单例的线程安全,底层结合了双重检查锁 + 容器缓存机制。

七、总结:选型指南

image.png

核心原则:
1、简单场景用饿汉式,需延迟加载用静态内部类;
2、对安全性要求极致时,优先选择枚举单例;
3、避免手动实现复杂的单例逻辑,优先利用框架(如 Spring)的单例管理能力。

最终目的:
保证核心对象全局唯一,减少资源消耗;

SpringBoot 核心应用场景:
Spring 容器中默认所有 Bean 为单例(singleton)、ApplicationContext实例全局唯一、核心工具类(如Environment)单例

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容