5.1 单例模式:饿汉式和懒汉式的减肥故事
graph TD
A[单例模式] --> B[饿汉式]
A --> C[懒汉式]
B --> D[类加载就初始化]
C --> E[延迟初始化]
E --> F[线程安全版]
E --> G[线程不安全版]
F --> H[双重检查锁]
F --> I[静态内部类]
F --> J[枚举实现]
饿汉式:提前囤粮的焦虑症患者
// 像囤积症患者,一上来就把食物塞满冰箱
public class EagerSingleton {
// 类加载时就初始化(JVM保证线程安全)
private static final EagerSingleton instance = new EagerSingleton();
// 把构造函数锁死,防止外部new实例
private EagerSingleton() {
if (instance != null) {
throw new RuntimeException("单例禁止反射攻击!");
}
}
public static EagerSingleton getInstance() {
return instance;
}
// 测试代码
public static void main(String[] args) {
EagerSingleton s1 = EagerSingleton.getInstance();
EagerSingleton s2 = EagerSingleton.getInstance();
System.out.println(s1 == s2); // 输出 true
}
}
特点:
- ✅ 线程安全(类加载机制保证)
- ❌ 可能浪费内存(即使不用也提前创建)
- ❌ 无法防止反射攻击(示例中已添加防御代码)
懒汉式:临时抱佛脚的减肥达人
// 像节食者,只有饿到不行才去找吃的
public class LazySingleton {
// volatile防止指令重排序(DCL必备)
private static volatile LazySingleton instance;
private LazySingleton() {}
// 双重检查锁(Double-Check Locking)
public static LazySingleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (LazySingleton.class) {
if (instance == null) { // 第二次检查
instance = new LazySingleton();
}
}
}
return instance;
}
// 测试多线程环境
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(10);
IntStream.range(0, 10).forEach(i ->
pool.submit(() ->
System.out.println(LazySingleton.getInstance().hashCode())
)
);
pool.shutdown();
}
}
执行结果: 所有线程输出的hashcode相同
特点:
- ✅ 延迟加载节省资源
- ✅ 线程安全(DCL+volatile)
- ❌ 实现较复杂
面试题加油站 ⛽
-
为什么单例模式要私有构造方法?
防止外部通过new创建实例,确保全局唯一性
-
DCL为什么要双重检查?
外层检查提高性能,内层检查防止多线程并发创建
-
volatile关键字在DCL中的作用?
禁止指令重排序,防止返回未初始化完成的对象
-
如何防止反射破坏单例?
在构造方法中判断实例是否已存在,若存在则抛出异常
-
枚举实现单例的优势?
天然防反射/反序列化攻击,代码简洁,推荐写法
-
单例模式违背了哪个设计原则?
单一职责原则(既管理实例创建又承担业务逻辑)
-
Spring框架中的单例是线程安全的吗?
默认单例是非线程安全的,需要开发者自行保证状态安全
扩展小剧场(图解)
sequenceDiagram
participant ThreadA
participant ThreadB
participant Singleton
ThreadA->>Singleton: getInstance()
Singleton-->>ThreadA: 第一次检查null
ThreadA->>Singleton: 获取锁
ThreadA->>Singleton: 第二次检查null
Singleton-->>ThreadA: 创建实例
ThreadA->>Singleton: 释放锁
ThreadB->>Singleton: getInstance()
Singleton-->>ThreadB: 第一次检查非null
Singleton-->>ThreadB: 直接返回实例