设计模式精讲:单例模式的多种实现与对比
单例模式是设计模式体系中最基础也最实用的一种,几乎在所有项目中都能找到它的身影。其核心目标是确保类在全局范围内只有一个实例,并提供统一的访问入口。虽然概念简单,但实现方式却有不少变种,以下逐一分析七种常见方案。
1. 饿汉式(静态常量)
这是最直接的实现方式,在类加载的时候就完成实例化,无论后续是否使用。
public class EagerSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
特点:线程安全(由类加载机制保证),但无懒加载效果。若类消耗资源且长时间未使用,会浪费内存。
适用场景:实例较小、实例化开销不高且频繁需要使用的类。
2. 懒汉式(非线程安全)
与饿汉式相反,实例仅在首次调用 getInstance() 时创建,实现懒加载。
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
问题:多线程环境下,多个线程可能同时进入 if (instance == null) 判断,导致创建多个实例,破坏单例。
3. 懒汉式 + 同步方法
在 getInstance() 方法上添加 synchronized 关键字解决线程安全问题。
public class LazySyncSingleton {
private static LazySyncSingleton instance;
private LazySyncSingleton() {}
public static synchronized LazySyncSingleton getInstance() {
if (instance == null) {
instance = new LazySyncSingleton();
}
return instance;
}
}
缺点:每次调用方法都需要获取锁,性能低下,实际上只有首次创建时才需要加锁。
4. 双重检查锁(Double-Checked Locking)
结合 synchronized 两次检查,只在实例为空时加锁,提升性能。
public class DclSingleton {
private static DclSingleton instance;
private DclSingleton() {}
public static DclSingleton getInstance() {
if (instance == null) {
synchronized (DclSingleton.class) {
if (instance == null) {
instance = new DclSingleton();
}
}
}
return instance;
}
}
隐患:instance = new DclSingleton() 在 JVM 中并非原子操作,可能被重排序,导致其他线程拿到未完全初始化的实例。
5. 双重检查锁 + volatile
使用 volatile 关键字禁止指令重排序,彻底解决上述隐患。
public class DclVolatileSingleton {
private static volatile DclVolatileSingleton instance;
private DclVolatileSingleton() {}
public static DclVolatileSingleton getInstance() {
if (instance == null) {
synchronized (DclVolatileSingleton.class) {
if (instance == null) {
instance = new DclVolatileSingleton();
}
}
}
return instance;
}
}
这是懒加载与线程安全兼顾的经典实现。
6. 静态内部类(Holder模式)
利用 Java 内部类的延迟加载机制实现线程安全的单例。
public class HolderSingleton {
private HolderSingleton() {}
private static class SingletonHolder {
private static final HolderSingleton INSTANCE = new HolderSingleton();
}
public static HolderSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
原理:静态内部类只在首次调用 getInstance() 时才会被加载并初始化,JVM 保障初始化过程的线程安全。兼具懒加载和高性能。
7. 枚举方式
《Effective Java》强烈推荐的方案,代码简洁且天然防序列化、反射攻击。
public enum EnumSingleton {
INSTANCE;
EnumSingleton() {
System.out.println("实例化");
}
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
缺点:无法实现懒加载(枚举常量在类加载时即会初始化)。
总结
| 实现方式 | 线程安全 | 懒加载 | 性能 | 推荐度 |
|---|---|---|---|---|
| 饿汉式 | 是 | 否 | 好 | ⭐⭐⭐ |
| 懒汉式(无同步) | 否 | 是 | - | 不推荐 |
| 懒汉式 + 同步方法 | 是 | 是 | 差 | 不推荐 |
| 双重检查锁 | 否(有BUG) | 是 | 好 | 不推荐 |
| 双重检查锁 + volatile | 是 | 是 | 好 | ⭐⭐⭐⭐ |
| 静态内部类 | 是 | 是 | 好 | ⭐⭐⭐⭐ |
| 枚举 | 是 | 否 | 好 | ⭐⭐⭐⭐⭐ |
实际开发中,优先推荐使用 枚举方式(若允许非懒加载)或 静态内部类(需懒加载时)。若需更精细的控制,可选择 双重检查锁 + volatile。