0112_单例模式
单例模式 (Singleton Pattern)
意图: 确保一个类只有一个实例,并提供一个全局访问点。
UML图:

下面对饿汉式单例、懒汉式单例、懒汉式单例-双重检查锁、静态内部类式、枚举单例、注册式单例进行说明。
1. 饿汉式单例 (Eager Singleton)
实例在类加载的初始化阶段就已经创建好了。JVM的类加载机制本身保证了线程安全。
public class EagerSingleton {
// 1. 静态私有成员,在类加载时即完成初始化
private static final EagerSingleton INSTANCE = new EagerSingleton();
// 2. 私有构造函数,防止外部new
private EagerSingleton() {}
// 3. 公共的静态方法,提供全局访问点
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
- 优点:
- 实现简单,代码易懂。
- 线程安全:由JVM在类加载时保证初始化一次,绝对安全。
- 缺点:
- 可能造成资源浪费:如果这个实例非常庞大,且在程序运行过程中从未被使用过,就是一种浪费。
- 无法传递参数:在实例化时无法通过
getInstance()方法传递初始化参数。
2. 懒汉式单例 (Lazy Singleton) - 基础线程不安全版
只有在调用getInstance()方法时才会创建实例。
public class UnsafeLazySingleton {
private static UnsafeLazySingleton instance;
private UnsafeLazySingleton() {}
public static UnsafeLazySingleton getInstance() {
// 如果instance为null,则创建新的实例
if (instance == null) {
instance = new UnsafeLazySingleton();
}
return instance;
}
}
- 优点:
- 延迟加载,避免了资源浪费。
- 缺点:
- 线程不安全:在多线程环境下,如果多个线程同时进入
if (instance == null)判断,可能会创建出多个实例。
- 线程不安全:在多线程环境下,如果多个线程同时进入
3. 懒汉式单例 (Lazy Singleton) - 双重检查锁 (Double-Checked Locking, DCL)
为了解决基础懒汉式的线程安全问题,并减少同步带来的性能开销,双重检查锁模式应运而生。
public class DclSingleton {
// 关键:使用volatile关键字禁止指令重排序
private static volatile DclSingleton instance;
private DclSingleton() {}
public static DclSingleton getInstance() {
// 第一次检查:避免不必要的同步
if (instance == null) {
synchronized (DclSingleton.class) {
// 第二次检查:确保线程进入同步块后实例仍未被创建
if (instance == null) {
instance = new DclSingleton(); // volatile保证了此操作的原子性和可见性
}
}
}
return instance;
}
}
- 优点:
- 延迟加载,资源利用率高。
- 线程安全。
- 性能较高:只有在实例未创建时才进行同步,之后调用无需进入同步块。
- 缺点:
- 实现稍复杂。
- 需要理解
volatile关键字和指令重排序的概念,早期JDK版本中实现有缺陷,现代JDK中已修复。
4. 静态内部类式 (Holder Class) - 推荐的懒汉式实现
这是一种更优雅的实现方式,利用了JVM的类加载机制来保证线程安全,同时实现了延迟加载。
public class HolderSingleton {
private HolderSingleton() {}
// 静态内部类持有单例实例
private static class InstanceHolder {
private static final HolderSingleton INSTANCE = new HolderSingleton();
}
public static HolderSingleton getInstance() {
// 首次调用getInstance方法才会加载InstanceHolder类并初始化INSTANCE
return InstanceHolder.INSTANCE;
}
}
- 优点:
- 延迟加载:只有在调用
getInstance()时,JVM才会加载InstanceHolder类并初始化INSTANCE。 - 线程安全:由JVM保证类加载过程的线程安全。
- 实现简单,无需
synchronized或volatile,性能高。
- 延迟加载:只有在调用
- 缺点:
- 无法传递参数。
5. 枚举单例 (Enum Singleton) - 《Effective Java》推荐方式
public enum EnumSingleton {
INSTANCE; // 唯一的实例
// 可以添加任意方法
public void doSomething() {
System.out.println("Doing something with " + this);
}
}
// 使用:EnumSingleton.INSTANCE.doSomething();
- 优点:
- 绝对线程安全且防止反序列化创建新实例。
- 防止反射攻击:JDK内部机制保证枚举类无法被反射实例化。
- 实现极其简单。
- 缺点:
- 不是懒加载(枚举实例在第一次被访问时初始化,可视为另一种“懒加载”)。
- 不够灵活(例如无法继承其他类)。
6. 注册式单例
这通常指的是将单例管理到一个容器(注册表)中,例如使用Map来缓存多种类型的单例对象。Spring框架中的IoC容器就是这种模式的极致体现。
public class SingletonRegistry {
private static final Map<String, Object> INSTANCES = new ConcurrentHashMap<>();
private SingletonRegistry() {}
public static Object getInstance(String className) {
synchronized (INSTANCES) {
if (!INSTANCES.containsKey(className)) {
try {
// 根据类名创建实例并放入Map
INSTANCES.put(className, Class.forName(className).newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
return INSTANCES.get(className);
}
}
}
- 优点:
- 管理多种单例,非常灵活。
- 符合“开闭原则”,易于扩展。
- 缺点:
- 需要处理并发问题,以实现线程安全。
- 实现相对复杂。
- 从容器中获取实例不如直接调用静态方法直观。
总结对比
| 模式 | 线程安全 | 延迟加载 | 防止反射/反序列化 | 实现难度 | 推荐度 |
|---|---|---|---|---|---|
| 饿汉式 | ✅ | ❌ | ❌ | 简单 | ⭐⭐⭐ |
| 懒汉式(不安全) | ❌ | ✅ | ❌ | 简单 | 不推荐 |
| 双重检查锁 | ✅ | ✅ | ❌ | 中等 | ⭐⭐⭐⭐ |
| 静态内部类 | ✅ | ✅ | ❌ | 简单 | ⭐⭐⭐⭐⭐ |
| 枚举 | ✅ | ⚪ | ✅ | 非常简单 | ⭐⭐⭐⭐⭐ |
| 注册式 | 需实现 | ✅ | 需实现 | 复杂 | ⭐⭐ |
建议:
- 如果需要延迟加载,优先选择静态内部类实现。
- 如果不需要延迟加载,或者实例很小,可以选择饿汉式。
- 如果需要绝对的安全(防止反射和反序列化破坏单例),必须选择枚举实现。
- 双重检查锁在需要传递初始化参数时仍有其用武之地。

浙公网安备 33010602011771号