自学教程 学编程从这里开始

单例模式

《单例模式》|设计模式系列第02篇

系列:创建型模式 · 单例模式(Singleton Pattern)
关键词:全局唯一实例、私有构造、懒汉/饿汉、双重检查锁、静态内部类、枚举单例

一、什么是单例模式

单例模式(Singleton Pattern):保证一个类在整个应用中只有一个实例对象,并且提供一个全局访问点来获取该实例。

属于创建型设计模式,核心两点:

  1. 类自身负责创建唯一实例
  2. 对外暴露统一方法获取实例,禁止外部new

核心意图

控制实例数量,节省资源;保证全局共用同一个对象,例如配置管理器、连接池、日志对象。

适用场景

  • 资源重量级对象:数据库连接池、线程池
  • 全局配置类、系统信息管理器
  • 日志工具类、缓存管理器
  • 需要全局唯一计数器

⚠️ 不适合:无状态轻量工具类,强行单例反而增加复杂度。

二、UML结构图

类:Singleton

  • 私有静态成员变量:instance(Singleton instance)
  • 私有构造方法:Singleton(),防止外部 new
  • 公共静态获取实例方法:getInstance()

三、常见实现方案(Java)

1. 饿汉式(Eager Singleton)

类加载时直接初始化实例,天生线程安全;缺点:类加载就创建,没有懒加载。

public class EagerSingleton {
    // 类加载直接实例化
    private static final EagerSingleton INSTANCE = new EagerSingleton();
    // 私有构造,禁止外部 new
    private EagerSingleton() {}

    // 全局访问点
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

✅ 优点:简单、线程安全
❌ 缺点:类加载即创建实例,浪费内存(实例从未使用时)

2. 懒汉式(Lazy Singleton,非线程安全)

第一次调用getInstance()才创建实例(懒加载),多线程环境会创建多个实例,生产禁用。

public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {}

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

✅ 优点:懒加载,用到才创建
❌ 缺点:多线程不安全

3. 双重检查锁 DCL(Double-Check Locking,推荐常用)

懒加载 + 线程安全,volatile 防止指令重排。

public class DclSingleton {
    // volatile 禁止指令重排
    private static volatile DclSingleton instance;

    private DclSingleton() {}

    public static DclSingleton getInstance() {
        if (instance == null) {          // 第一次检查
            synchronized (DclSingleton.class) {
                if (instance == null) {  // 第二次检查
                    instance = new DclSingleton();
                }
            }
        }
        return instance;
    }
}

✅ 优点:懒加载、线程安全、性能好
❌ 缺点:实现稍复杂;反射可以破坏单例

关键点:instance = new DclSingleton() 并非原子操作,volatile 必不可少。

4. 静态内部类(Static Inner Class,推荐)

利用类加载机制实现懒加载,线程安全,写法简洁。

public class StaticInnerSingleton {
    private StaticInnerSingleton() {}

    // 静态内部类,只有调用时才加载
    private static class SingletonHolder {
        private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();
    }

    public static StaticInnerSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

✅ 优点:懒加载、线程安全、代码简洁
❌ 缺点:依然可以被反射破坏

5. 枚举单例(Enum Singleton,Effective Java 推荐)

防止反射、序列化破坏单例,最安全。

public enum EnumSingleton {
    INSTANCE;

    public void doSomething() {
        System.out.println("枚举单例业务方法");
    }
}
// 使用:EnumSingleton.INSTANCE.doSomething();

实例调用演示代码:

/**
 * 单例模式演示:验证五种实现均为同一实例。
 */
public class SingletonDemo {

    public static void main(String[] args) {
        System.out.println("========== 1. 饿汉式 ==========");
        EagerSingleton eager1 = EagerSingleton.getInstance();
        EagerSingleton eager2 = EagerSingleton.getInstance();
        eager1.sayHello();
        System.out.println("同一实例: " + (eager1 == eager2));

        System.out.println("\n========== 2. 懒汉式 ==========");
        LazySingleton lazy1 = LazySingleton.getInstance();
        LazySingleton lazy2 = LazySingleton.getInstance();
        lazy1.sayHello();
        System.out.println("同一实例: " + (lazy1 == lazy2));

        System.out.println("\n========== 3. 双重检查锁 DCL ==========");
        DclSingleton dcl1 = DclSingleton.getInstance();
        DclSingleton dcl2 = DclSingleton.getInstance();
        dcl1.sayHello();
        System.out.println("同一实例: " + (dcl1 == dcl2));

        System.out.println("\n========== 4. 静态内部类 ==========");
        StaticInnerSingleton inner1 = StaticInnerSingleton.getInstance();
        StaticInnerSingleton inner2 = StaticInnerSingleton.getInstance();
        inner1.sayHello();
        System.out.println("同一实例: " + (inner1 == inner2));

        System.out.println("\n========== 5. 枚举单例 ==========");
        EnumSingleton.INSTANCE.doSomething();
        System.out.println("同一实例: " + (EnumSingleton.INSTANCE == EnumSingleton.INSTANCE));
    }
}

执行结果:

✅ 优点:线程安全、防反射、防序列化破坏,代码极简
❌ 缺点:饿汉加载,不支持懒加载

四、单例的破坏方式

  1. 反射:通过反射调用私有构造器,创建新实例
  2. 序列化 & 反序列化:反序列化默认生成新对象(枚举除外)
  3. 多类加载器:不同类加载器会加载多份class,产生多个实例

五、优缺点总结

✅ 优点

  1. 全局唯一实例,节约系统资源
  2. 提供统一访问入口,方便管控
  3. 减少对象创建销毁开销

❌ 缺点

  1. 单例对象持有引用,容易造成内存泄漏
  2. 代码耦合,不利于单元测试(难以mock)
  3. 违背单一职责:既要管理实例,又要实现业务逻辑
  4. 必须处理多线程、反射、序列化问题

六、框架中的实际应用

  • Spring:Bean 默认单例(singleton scope)
  • Runtime.getRuntime() JDK运行时对象,典型单例
  • MyBatis ErrorContext 使用单例

七、小结

  • 饿汉:简单,无懒加载
  • DCL:懒加载高性能,注意volatile
  • 静态内部类:平衡之选
  • 枚举单例:安全性最高,优先推荐(不需要懒加载场景)
标签:

0 条笔记