Skip to content

枚举单例模式

写单例,我们通常关心三个问题:

  • 线程安全吗?
  • 能防反射攻击吗?
  • 反序列化时还是同一个对象吗?

大多数单例实现只解决了第一个问题。只有枚举,三个问题同时解决。

为什么枚举单例最安全

方式线程安全防反射防反序列化
饿汉式
懒汉式(同步方法)
双重检查锁定
静态内部类
枚举

原理很简单:枚举的构造方法在类加载时由 JVM 调用,且只能调用一次。JVM 保证了枚举实例的唯一性和线程安全性。

代码示例

基本实现

java
public class EnumSingletonDemo {

    enum Singleton {
        INSTANCE;  // 唯一的实例

        private String data;

        public String getData() {
            return data;
        }

        public void setData(String data) {
            this.data = data;
        }
    }

    public static void main(String[] args) {
        Singleton singleton = Singleton.INSTANCE;
        singleton.setData("Hello");
        System.out.println(singleton.getData());
    }
}

完整示例(带延迟初始化)

java
public class EnumSingletonAdvancedDemo {

    enum DatabaseConnection {
        INSTANCE;

        private String url;
        private String username;
        private String password;
        private boolean initialized = false;

        // JVM 保证构造方法只调用一次
        DatabaseConnection() {
            System.out.println("Connecting to database...");
            this.url = "jdbc:mysql://localhost:3306";
            this.username = "root";
            this.password = "password";
            this.initialized = true;
        }

        public void query(String sql) {
            if (!initialized) throw new IllegalStateException("Not initialized");
            System.out.println("Executing: " + sql);
        }

        public void close() {
            System.out.println("Closing connection");
        }
    }

    public static void main(String[] args) {
        // 第一次访问时初始化
        DatabaseConnection db = DatabaseConnection.INSTANCE;
        db.query("SELECT * FROM users");
        db.close();

        // 多次获取仍是同一个对象
        DatabaseConnection db2 = DatabaseConnection.INSTANCE;
        System.out.println("Same instance: " + (db == db2));  // true
    }
}

对比其他单例实现

java
public class SingletonComparison {

    // ❌ 懒汉式(线程不安全)
    static class LazySingleton {
        private static LazySingleton instance;
        private LazySingleton() {}
        public static LazySingleton getInstance() {
            if (instance == null) {
                instance = new LazySingleton();
            }
            return instance;
        }
    }

    // ❌ 饿汉式(无法延迟加载)
    static class HungrySingleton {
        private static final HungrySingleton instance = new HungrySingleton();
        private HungrySingleton() {}
        public static HungrySingleton getInstance() {
            return instance;
        }
    }

    // ❌ 双重检查(繁琐)
    static class DoubleCheckedSingleton {
        private static volatile DoubleCheckedSingleton instance;
        private DoubleCheckedSingleton() {}
        public static DoubleCheckedSingleton getInstance() {
            if (instance == null) {
                synchronized (DoubleCheckedSingleton.class) {
                    if (instance == null) {
                        instance = new DoubleCheckedSingleton();
                    }
                }
            }
            return instance;
        }
    }

    // ✅ 枚举(简洁安全)
    enum BestSingleton {
        INSTANCE;
    }
}

注意事项

  1. 首选枚举:Effective Java 作者 Joshua Bloch 推荐的方式
  2. 简洁即正确:不需要同步关键字,不需要双重检查
  3. 延迟初始化:枚举实例在首次使用时才创建
  4. 防反射:JVM 阻止通过反射调用枚举构造方法
  5. 需要复杂初始化时:可以将初始化逻辑放在 static {} 代码块中

枚举单例不是银弹。对于需要延迟加载初始化成本极高的场景,静态内部类仍是更好的选择。但对于绝大多数业务场景,枚举单例是最简洁、最安全的方案。

基于 VitePress 构建