跳转至

如何用 new 实现线程安全的单例模式?

单例模式最核心的矛盾就是:又要全局唯一,又要懒加载,还要线程安全。 用 new 来实现的话,常见有三条路,我按推荐程度从高到低说一下。


🥇 推荐:静态内部类(Holder 模式)

既能懒加载(真正调用时才初始化),又是线程安全(JVM 类加载机制保证),代码还简单。

public class Singleton {
    private Singleton() {}   // 构造私有

    private static class Holder {
        // 内部类加载时才执行这个 new,且只执行一次
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

✅ 原理:Singleton 类加载时,内部类 Holder 不会被加载。只有 getInstance() 第一次被调用,触发 Holder 类的初始化,JVM 会确保这个初始化过程是线程安全的,new Singleton() 只执行一次。 ✅ 没有锁,没有 volatile,性能最高。


🥈 备选:双重检查锁定(DCL)

如果面试官问“怎么用 new 加锁实现”,那这就是经典写法。必须配合 volatile,否则指令重排可能让其他线程拿到“半成品”对象。

public class Singleton {
    private static volatile Singleton instance;   // volatile 防止指令重排

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {                   // 第一次检查,避免不必要的同步
            synchronized (Singleton.class) {
                if (instance == null) {           // 第二次检查,保证只 new 一次
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

⚠️ 为什么必须 volatilenew Singleton() 实际分三步: ① 分配内存 ② 调用构造函数初始化 ③ 将引用指向内存地址

如果没有 volatile,②和③可能被重排,导致另一个线程在判断 instance != null 时拿到一个还未初始化完成的对象。volatile 禁止这种重排。


🥉 最简单粗暴:饿汉式

类一加载就直接 new,天生线程安全,但不懒加载。如果单例创建成本高且不一定会用到,可能浪费资源。

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

✅ 基于类加载机制的线程安全,代码极简。

❌ 失去延迟初始化优势。


🏅 额外加分:枚举单例

虽然没有显式的 new,但它是《Effective Java》推荐的最安全写法,能防反射、防序列化破坏。

public enum Singleton {
    INSTANCE;
    // 可以加方法
    public void doSomething() {}
}

不过因为问题指定了“用 new”,这个作为知识拓展提一嘴就好。