1. 简介
本文将快速介绍两种最主流的纯Java单例模式实现方式。
2. 基于类的单例模式
最常见的方式是创建一个普通类并确保它满足以下条件:
- 私有构造函数
- 静态字段存储唯一实例
- 静态工厂方法获取实例
我们添加一个info
属性用于后续演示。实现如下:
public final class ClassSingleton {
private static ClassSingleton INSTANCE;
private String info = "Initial info class";
private ClassSingleton() {
}
public static ClassSingleton getInstance() {
if(INSTANCE == null) {
INSTANCE = new ClassSingleton();
}
return INSTANCE;
}
// getters and setters
}
⚠️ 虽然这是常见实现,但在多线程场景下存在严重问题——而这正是使用单例模式的核心场景。简单说,它可能创建多个实例,破坏单例原则。虽然可以用锁解决,但下一种实现能从根本上避免这个问题。
3. 枚举单例模式
另一种巧妙的方式是使用枚举实现:
public enum EnumSingleton {
INSTANCE("Initial class info");
private String info;
private EnumSingleton(String info) {
this.info = info;
}
public EnumSingleton getInstance() {
return INSTANCE;
}
// getters and setters
}
✅ 这种实现由枚举机制天然保证:
- 线程安全
- 序列化安全
完美解决了基于类实现的问题。
4. 使用方式
使用ClassSingleton
只需静态获取实例:
ClassSingleton classSingleton1 = ClassSingleton.getInstance();
System.out.println(classSingleton1.getInfo()); //Initial class info
ClassSingleton classSingleton2 = ClassSingleton.getInstance();
classSingleton2.setInfo("New class info");
System.out.println(classSingleton1.getInfo()); //New class info
System.out.println(classSingleton2.getInfo()); //New class info
而EnumSingleton
的使用方式与普通枚举一致:
EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE.getInstance();
System.out.println(enumSingleton1.getInfo()); //Initial enum info
EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE.getInstance();
enumSingleton2.setInfo("New enum info");
System.out.println(enumSingleton1.getInfo()); // New enum info
System.out.println(enumSingleton2.getInfo()); // New enum info
5. 常见陷阱
单例模式看似简单,实际开发中容易踩坑。问题可分为两类:
- 设计层面(是否真的需要单例?)
- 实现层面(是否正确实现了单例?)
5.1. 设计层面问题
本质上单例是一种全局变量。众所周知,全局变量应尽量避免使用,尤其是可变状态的全局变量。
我们不是说要彻底禁用单例,而是提醒:可能有更优雅的代码组织方式。
如果方法依赖单例对象,为什么不直接作为参数传入?这样能显式声明依赖关系,测试时也方便mock这些依赖。
典型踩坑场景:用单例存储应用配置(如数据库连接)。如果作为全局对象使用,测试环境将难以切换配置。最终导致测试数据污染生产数据库——这显然不可接受。
如果必须用单例,考虑将其实例化委托给专门的工厂类,由工厂保证单例的唯一性。
5.2. 实现层面问题
即使看似简单的单例实现,也可能因各种问题导致出现多个实例。
同步问题
前文私有构造函数的实现非线程安全。单线程环境没问题,但多线程下必须同步操作:
public synchronized static ClassSingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new ClassSingleton();
}
return INSTANCE;
}
注意synchronized
关键字。方法包含多个操作(比较、实例化、返回),若无同步:
- 线程A和线程B可能同时判断
INSTANCE == null
为true
- 导致创建两个
ClassSingleton
实例
❌ 同步会显著影响性能。高频调用场景可考虑:
- 懒加载
- 双重检查锁定(⚠️ 注意:因编译器优化可能失效)
多实例问题
JVM层面也可能导致意外多实例:
- 单例本应JVM内唯一,但在分布式系统或基于分布式技术的系统中可能失效
- 不同类加载器可能加载各自的单例版本
- 单例可能被GC回收(无引用时)。虽然不会同时存在多实例,但重建后的实例状态可能与之前不同
6. 总结
本文聚焦纯Java实现单例模式的核心方案,探讨了如何保证一致性及实际应用场景。选择实现方式时需权衡线程安全、序列化需求和性能开销。