枚举单例模式
写单例,我们通常关心三个问题:
- 线程安全吗?
- 能防反射攻击吗?
- 反序列化时还是同一个对象吗?
大多数单例实现只解决了第一个问题。只有枚举,三个问题同时解决。
为什么枚举单例最安全
| 方式 | 线程安全 | 防反射 | 防反序列化 |
|---|---|---|---|
| 饿汉式 | ✅ | ❌ | ❌ |
| 懒汉式(同步方法) | ✅ | ❌ | ❌ |
| 双重检查锁定 | ✅ | ❌ | ❌ |
| 静态内部类 | ✅ | ❌ | ❌ |
| 枚举 | ✅ | ✅ | ✅ |
原理很简单:枚举的构造方法在类加载时由 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;
}
}注意事项
- 首选枚举:Effective Java 作者 Joshua Bloch 推荐的方式
- 简洁即正确:不需要同步关键字,不需要双重检查
- 延迟初始化:枚举实例在首次使用时才创建
- 防反射:JVM 阻止通过反射调用枚举构造方法
- 需要复杂初始化时:可以将初始化逻辑放在
static {}代码块中
枚举单例不是银弹。对于需要延迟加载且初始化成本极高的场景,静态内部类仍是更好的选择。但对于绝大多数业务场景,枚举单例是最简洁、最安全的方案。
