单例模式
确保类只有一个实例,并提供全局访问点。详解 设计模式。
要求
- 构造器私有
- 提供静态获取实例方法
- 线程安全(多线程环境)
- 防反射、反序列化破坏(面试加分项)
实现方式对比
| 方式 | 线程安全 | 延迟加载 | 防反射/序列化 | 推荐 |
|---|---|---|---|---|
| 饿汉式 | ✅ | ❌ 类加载即创建 | ❌ | 简单场景 |
| 懒汉 synchronized | ✅ | ✅ | ❌ | 性能差 |
| 双重检查锁 DCL | ✅(volatile) | ✅ | ❌ | 常用 |
| 静态内部类 | ✅ | ✅ | ❌ | 推荐 |
| 枚举 | ✅ | ❌ 枚举加载 | ✅ | 最简最安全 |
双重检查锁(DCL)
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}为何 volatile:new 非原子,可能重排序导致其他线程看到未初始化完成的对象。
静态内部类(推荐)
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}JVM 保证内部类首次使用时才加载,天然 lazy + 线程安全。
枚举(Effective Java 推荐)
public enum Singleton {
INSTANCE;
public void doWork() { /* ... */ }
}JVM 保证枚举实例唯一;天然防反射和序列化破坏。
破坏单例的方式(面试追问)
| 手段 | 防御 |
|---|---|
| 反射调用私有构造 | 构造器内判 instance != null 抛异常;或改用枚举 |
| 反序列化新对象 | readResolve() 返回 INSTANCE;或枚举 |
| 克隆 | 禁 Cloneable 或 clone 返回 INSTANCE |
Spring 中的「单例」
Spring 默认 Bean 作用域是 singleton(容器内一个实例),与 GoF 单例模式概念相近但作用域不同(Per Container)。见 Bean作用域。
相关笔记
- Java多线程与JUC — volatile、synchronized
- 设计模式