Skip to content

单例模式 (Singleton Pattern) ​

模式定义 ​

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取该实例。

核心要点 ​

  • 私有构造函数,防止外部实例化
  • 提供静态方法或字段获取唯一实例
  • 线程安全的实现方式

实现方式 ​

1. 饿汉式 ​

类加载时就立即创建实例,线程安全,但无法延迟加载。

java
public class HungrySingleton {
    // 类加载时就初始化
    private static final HungrySingleton INSTANCE = new HungrySingleton();

    // 私有构造函数
    private HungrySingleton() {}

    public static HungrySingleton getInstance() {
        return INSTANCE;
    }

    public void doSomething() {
        System.out.println("HungrySingleton is working.");
    }
}

优点: 实现简单,线程安全。 缺点: 类加载时就创建,无论是否使用,浪费内存。


2. 双重检查锁 (DCL) ​

延迟加载 + 线程安全,是生产环境最常用的单例实现。

java
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;
    }

    public void doSomething() {
        System.out.println("DCLSingleton is working.");
    }
}

为什么需要 volatile?instance = new DCLSingleton() 这行代码实际分为三步:

  1. 分配内存空间
  2. 初始化对象
  3. 将引用指向分配的内存

JVM 可能会对 2 和 3 进行重排序,导致其他线程看到未初始化的对象。volatile 禁止重排序。


3. 静态内部类 ​

利用类加载机制实现延迟加载,同时保证线程安全。

java
public class InnerClassSingleton {
    private InnerClassSingleton() {}

    // 静态内部类,只有在被引用时才会加载
    private static class SingletonHolder {
        private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }

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

    public void doSomething() {
        System.out.println("InnerClassSingleton is working.");
    }
}

优点: 延迟加载 + 线程安全,无锁,性能好。 缺点: 无法传参初始化。


4. 枚举单例 ​

《Effective Java》推荐的方式,天然防止反射和序列化攻击。

java
public enum EnumSingleton {
    INSTANCE;

    private int count;

    public void doSomething() {
        System.out.println("EnumSingleton is working. count=" + count);
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}

// 使用方式
// EnumSingleton.INSTANCE.doSomething();

优点: 简洁,天然线程安全,防止反射和序列化破坏。 缺点: 不支持延迟加载。


5. 序列化防御 ​

防止反序列化创建新实例。

java
public class SerializableSingleton implements Serializable {
    private static final long serialVersionUID = 1L;

    private static volatile SerializableSingleton instance;

    private SerializableSingleton() {}

    public static SerializableSingleton getInstance() {
        if (instance == null) {
            synchronized (SerializableSingleton.class) {
                if (instance == null) {
                    instance = new SerializableSingleton();
                }
            }
        }
        return instance;
    }

    // 关键:反序列化时返回同一个实例
    private Object readResolve() {
        return instance;
    }

    public void doSomething() {
        System.out.println("SerializableSingleton is working.");
    }
}

原理: Java 序列化机制在反序列化时,如果类定义了 readResolve() 方法,会调用该方法返回对象,从而保证单例。


6. 反射防御 ​

防止通过反射创建新实例。

java
public class ReflectionSingleton {
    private static volatile ReflectionSingleton instance;
    private static boolean isCreated = false;

    private ReflectionSingleton() {
        // 防御反射攻击
        if (isCreated) {
            throw new RuntimeException("请使用 getInstance() 方法获取实例!");
        }
        isCreated = true;
    }

    public static ReflectionSingleton getInstance() {
        if (instance == null) {
            synchronized (ReflectionSingleton.class) {
                if (instance == null) {
                    instance = new ReflectionSingleton();
                }
            }
        }
        return instance;
    }

    public void doSomething() {
        System.out.println("ReflectionSingleton is working.");
    }
}

原理: 通过标志位判断是否已经创建过实例,如果是通过反射创建的,则抛出异常。


7. ThreadLocal 单例 ​

保证每个线程有自己的单例实例(伪线程单例)。

java
public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> THREAD_LOCAL =
            ThreadLocal.withInitial(ThreadLocalSingleton::new);

    private ThreadLocalSingleton() {}

    public static ThreadLocalSingleton getInstance() {
        return THREAD_LOCAL.get();
    }

    public void doSomething() {
        System.out.println("ThreadLocalSingleton is working in thread: "
                + Thread.currentThread().getName());
    }
}

特点: 每个线程获取到的是不同的实例,适用于线程隔离场景。


各实现方式对比 ​

实现方式线程安全延迟加载防反射防序列化推荐场景
饿汉式✅❌❌❌实例占用资源少
DCL✅✅❌❌通用场景
静态内部类✅✅❌❌通用场景,性能要求高
枚举✅❌✅✅强烈推荐
序列化防御✅✅❌✅需要序列化的场景
反射防御✅✅✅❌安全性要求高
ThreadLocal线程内✅❌❌线程隔离场景

实际应用 ​

  • Spring Bean: 默认 scope=singleton,容器中只有一个 Bean 实例
  • 数据库连接池: 保证全局唯一连接池
  • 配置管理器: 全局配置信息
  • 日志记录器: 全局日志实例
  • Runtime 类: Runtime.getRuntime() 就是饿汉式单例

注意事项 ​

  1. 线程安全 是单例实现的首要考虑因素
  2. DCL 中 volatile 关键字不可省略
  3. 枚举 是最安全的单例实现方式
  4. 多线程环境下,避免使用简单同步(性能差)
  5. 序列化和反射是单例的两大"杀手",需要额外防御