Skip to content
极高进阶

一句话答案

保证类只有一个实例,推荐枚举实现(最安全)或双重检查锁 DCL(volatile 禁止重排防止获取未初始化对象)。

核心要点

单例模式确保一个类只有一个实例,并提供全局访问点。常见写法有四种:

1. 饿汉式(推荐用于简单场景)

java
public class Singleton {
    // 类加载时即初始化,JVM 保证线程安全
    private static final Singleton INSTANCE = new Singleton();
    
    private Singleton() {} // 私有构造
    
    public static Singleton getInstance() {
        return INSTANCE;
    }
}
  • 优点:实现简单,线程安全(JVM 保证 static 变量只初始化一次)
  • 缺点:类加载就实例化,不支持懒加载

2. 懒汉式 DCL(Double-Checked Locking)

java
public class Singleton {
    // 必须加 volatile!
    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;
    }
}

DCL 为什么必须加 volatile?

INSTANCE = new Singleton() 不是原子操作,实际分三步:

  1. 分配内存空间
  2. 初始化对象(调用构造方法)
  3. 将引用指向分配的内存

JVM 可能对步骤 2 和 3 进行指令重排序,变成 1→3→2。如果线程 A 执行了 1→3 但还未执行 2,此时线程 B 在第一次检查时发现 INSTANCE != null,直接返回了一个未完成初始化的对象,导致 NPE 或数据异常。

volatile 通过内存屏障禁止指令重排序,保证 1→2→3 的顺序执行。→ 详见 Module 04 JMM 部分

3. 静态内部类(推荐)

java
public class Singleton {
    private Singleton() {}
    
    // 静态内部类在外部类加载时不会被加载,实现懒加载
    // 内部类加载时 JVM 保证线程安全
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}
  • 兼具懒加载 + 线程安全,无锁性能好

4. 枚举(终极写法,Effective Java 推荐)

java
public enum Singleton {
    INSTANCE;
    
    public void doSomething() {
        // 业务方法
    }
}
  • JVM 保证唯一实例
  • 天然防止反射攻击和反序列化破坏单例
  • 缺点:不支持懒加载,写法不够直观

对比总结:

写法线程安全懒加载防反射推荐度
饿汉式★★★
DCL是(需 volatile)★★★★
静态内部类★★★★★
枚举★★★★★
追问与易错

追问方向:

  • DCL 为什么需要 volatile?(防止对象未初始化就被使用,指令重排问题)
  • 枚举单例为什么最安全?(天然防反射和序列化攻击)
  • Spring 的单例和设计模式的单例有什么区别?(Spring 是容器级别的单例,不是类级别)

易错点:

  • ❌ "饿汉式有性能问题"——类不使用不会加载,实际很少浪费
  • ❌ "DCL 不加 volatile 也行"——JDK5 之前确实有问题(指令重排)

💡 记忆锚点

全校只许有一个校长:饿汉式开学就任命(简单但不懒加载),DCL是有人找校长时才选(volatile防选到还没穿好校服的),静态内部类是密封信封里的任命书(拆封才生效),枚举是写入宪法的终身制(防篡改防克隆最安全)。