单例模式(Singleton Pattern)是软件设计模式中的一种,用于确保一个类只有一个实例,并提供一个全局访问点。虽然实现单例模式看似简单,但在多线程环境下若处理不当,可能导致实例多次创建,破坏单例的核心原则。下面介绍几种在Java中实现线程安全单例模式的常用方法,并讨论它们的优缺点。
1. 饿汉式(Eager Initialization)
public class SingletonEager {
private static final SingletonEager INSTANCE = new SingletonEager();
private SingletonEager() { }
public static SingletonEager getInstance() {
return INSTANCE;
}
}
- 优点:实现最简单,线程安全,类加载时即完成实例化,避免了懒加载带来的性能问题。
- 缺点:若实例化过程耗费资源,甚至因为不可用导致启动失败,无法延迟实例化。
2. 双重检查锁(Double-Check Locking)
public class SingletonDCL {
private static volatile SingletonDCL instance;
private SingletonDCL() { }
public static SingletonDCL getInstance() {
if (instance == null) {
synchronized (SingletonDCL.class) {
if (instance == null) {
instance = new SingletonDCL();
}
}
}
return instance;
}
}
- 原理:第一次检查实例是否已创建,若未创建才进入同步块,第二次检查确保多线程不重复创建。
- 关键点:
instance必须声明为volatile,防止指令重排导致线程看到未初始化的对象。 - 优点:延迟实例化,且只有第一次创建时才同步,性能相对较好。
- 缺点:实现稍显复杂,错误实现会导致线程安全问题。
3. 静态内部类(Initialization-on-Demand Holder Idiom)
public class SingletonHolder {
private SingletonHolder() { }
private static class Holder {
private static final SingletonHolder INSTANCE = new SingletonHolder();
}
public static SingletonHolder getInstance() {
return Holder.INSTANCE;
}
}
- 原理:外部类引用内部类时,内部类不会立即被加载,只有在调用
getInstance()时才会加载,从而实现懒加载。 - 优点:实现简单,天然线程安全,性能优越。
- 缺点:需要Java 5及以上版本,内部类的使用场景不常见。
4. 枚举实现(Enum Singleton)
public enum SingletonEnum {
INSTANCE;
public void someMethod() {
// 单例方法
}
}
- 原理:Java枚举在类加载时完成实例化,且对序列化和反射都有天然的防护。
- 优点:极简实现,天然支持序列化且防止反射攻击。
- 缺点:不支持延迟加载,且不太符合传统类的单例实现习惯。
5. 基于 java.util.concurrent 的 Lazy 实现
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
public class LazySingleton <T> {
private final AtomicReference <T> instance = new AtomicReference<>();
private final Supplier <T> supplier;
public LazySingleton(Supplier <T> supplier) {
this.supplier = supplier;
}
public T getInstance() {
if (instance.get() == null) {
instance.compareAndSet(null, supplier.get());
}
return instance.get();
}
}
-
使用示例:
LazySingleton <MySingleton> lazySingleton = new LazySingleton<>(MySingleton::new); MySingleton singleton = lazySingleton.getInstance(); -
优点:通过
AtomicReference保证原子性,支持任意类型的懒加载。 -
缺点:使用时需要额外的包装类,使用复杂度略高。
如何选择合适的实现?
| 场景 | 推荐实现 | 说明 |
|---|---|---|
| 简单项目,资源不耗费 | 饿汉式 | 简单、可靠 |
| 需要延迟加载,性能关注 | 双重检查锁或静态内部类 | 性能均衡 |
| 需要防止序列化/反射攻击 | 枚举 | 极简安全 |
| 需要通用懒加载模板 | LazySingleton |
高度灵活 |
结语
Java中实现线程安全单例有多种成熟方案。根据实际需求选择合适的实现方式,既能保证线程安全,又能满足性能和安全性的要求。熟练掌握这些模式后,你可以在任何需要全局唯一实例的场景中迅速、稳健地使用单例模式。

发表回复