Java设计模式:单例模式的几种实现

单例模式是最简单也最常考的设计模式,但要写出线程安全且高效的实现并不容易。这篇文章梳理五种主流写法,分析各自的优缺点。

为什么需要单例

某些对象在整个应用中只需要一个实例,比如配置管理器、数据库连接池、线程池等。单例模式确保一个类只有一个实例,并提供全局访问点。

方式一:饿汉式

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

    private Singleton() {}

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

类加载时就创建实例,线程安全,实现简单。缺点是无论是否使用都会创建对象,如果初始化开销大会浪费资源。实际开发中,大多数场景下这个"缺点"并不是问题,因为单例类通常都会被用到。

方式二:懒汉式(线程不安全)

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

延迟加载,首次调用时才创建。但多线程环境下,两个线程可能同时通过 instance == null 判断,导致创建多个实例。不要在多线程环境中使用这种写法。

方式三:双重检查锁(DCL)

public class Singleton {
    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;
    }
}

volatile 关键字不可省略。没有它,instance = new Singleton() 可能发生指令重排序:先给 instance 赋引用地址,再执行构造函数。此时另一个线程读到的 instance 不为 null,但对象尚未初始化完成,会产生诡异的错误。

这种写法兼顾了延迟加载和线程安全,性能也不错——只有第一次创建时会进入同步块。

方式四:静态内部类

public class Singleton {
    private Singleton() {}

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

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

利用 JVM 类加载机制保证线程安全:内部类 Holder 在第一次被引用时才加载,加载过程由 JVM 保证线程安全。同时实现了延迟加载——只有调用 getInstance() 时才会触发 Holder 的加载。

这是公认最优雅的写法,既线程安全又延迟加载,没有同步开销。

方式五:枚举

public enum Singleton {
    INSTANCE;

    public void doSomething() {
        System.out.println("枚举单例");
    }
}

// 使用
// Singleton.INSTANCE.doSomething();

《Effective Java》推荐的方式。枚举天然是线程安全的单例,而且能防止反序列化和反射攻击创建新实例——这是前面四种方式都无法做到的。缺点是写法不够直观,继承 enum 后不能再继承其他类。

各方式对比

方式 线程安全 延迟加载 防反射/反序列化 推荐度
饿汉式 适合大多数场景
懒汉式 不推荐
双重检查锁 推荐
静态内部类 推荐
枚举 最安全

没有绝对最好的方式,根据场景选择:普通业务用饿汉式或静态内部类即可,对安全性有极高要求的场景用枚举。