“单例模式是设计模式中最简单的形式之一,却也是面试中最常被深挖的考点。” ——《设计模式:可复用面向对软件的基础》
1. 什么是单例模式?
单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类仅有一个实例存在,并提供一个全局访问点供外部获取该实例。数学逻辑中的singleton定义为“有且仅有一个元素的集合”,在软件设计中则体现为对唯一实例的严格控制。
2. 三大核心特征
- 唯一实例:类只能创建一个对象实例
- 自我创建:实例由类自身创建并管理
- 全局访问:提供统一的静态访问入口
3. 典型应用场景
| 应用场景 | 实例说明 | 必要性分析 |
|---|---|---|
| 配置管理器 | 统一管理应用配置参数 | 避免配置信息不一致 |
| 线程池/连接池 | 数据库连接池、线程池 | 防止资源竞争和超限分配 |
| 日志系统 | 全局日志记录器 | 确保日志写入顺序一致性 |
| 对话框/弹窗 | 系统级对话框 | 避免重复弹窗干扰用户 |
| 设备驱动 | 打印机、显卡驱动访问 | 防止硬件指令冲突 |
二、单例模式的六大实现方式及原理剖析
1. 饿汉式(线程安全)
public class EagerSingleton {
// 类加载时立即初始化(线程安全)
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() { // 私有构造器阻外部实例化 }
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
特点:
- 启动即创建:类加载时静态初始化实例
- 绝对线程安全:由JVM类加载机制保证
- 资源浪费风险:未使用时仍占用内存
2. 懒汉式基础版(线程不安全)
public class UnsafeLazySingleton {
private static UnsafeLazySingleton instance;
private UnsafeLazySingleton() {}
public static UnsafeLazySingleton getInstance() {
if (instance == null) {
instance = new UnsafeLazySingleton(); // 多线程下可能创建多个实例
}
return instance;
}
}
致命缺陷:并发创建漏洞——多个线程同时通过null检查时,会实例化多个对象
3. 同步锁懒汉式(线程安全但低效)
public class SynchronizedLazySingleton {
private static SynchronizedLazySingleton instance;
private SynchronizedLazySingleton() {}
// 方法级同步锁
public static synchronized SynchronizedLazySingleton getInstance() {
if (instance == null) {
instance = new SynchronizedLazySingleton();
}
return instance;
}
}
优劣对比:
- 解决线程安全问题
- 性能瓶颈:每次调用都同步,并发高时效率骤降
4. 双重检查锁(DCL)优化版
public class DoubleCheckedSingleton {
private static volatile DoubleCheckedSingleton instance; // volatile确保可见性
private DoubleCheckedSingleton() {}
public static DoubleCheckedSingleton getInstance() {
if (instance == null) { // 第一次检查(无锁)
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) { // 第二次检查(有锁)
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}
关键技术点:
- 双重判空:外层避免不必要的锁竞争,内层防止重复创建
- volatile:禁止指令重排序,解决"半初始化"问题
- 锁粒度优化:仅实例化阶段需要同步
⚠️ 注意:JDK 5+的volatile内存语义修正后此模式才安全
5. 静态内部类(推荐方案)
public class InnerClassSingleton {
private InnerClassSingleton() {}
// 静态内部类持有实例
private static class SingletonHolder {
private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE; // 首次调用时加载内部类
}
}
优势:
- 懒加载:调用
getInstance()时才加载SingletonHolder类 - 无锁线程安全:JVM类加载机制保证初始化安全
- 高性能:无同步开销
6. 枚举单例(最安全方案)
public enum EnumSingleton {
INSTANCE; // 单例实例
// 业务方法
public void doSomething() {
System.out.println("Singleton working");
}
}
// 使用方式:EnumSingleton.INSTANCE.doSomething();
不可替代的优势:
- 绝对单例:JVM保证枚举实例唯一性
- 防反射攻击:无法通过反射创建新实例
- 序列化安全:自动处理序列化/反序列化
- 代码简洁:无需手动实现安全逻辑
三、单例模式进阶:陷阱防范与最佳实践
1. 反射攻击的防御策略
通过构造器检测阻止反射创建实例:
public class ReflectionProofSingleton {
private static volatile ReflectionProofSingleton instance;
private ReflectionProofSingleton() {
// 防止反射破坏单例
if (instance != null) {
throw new IllegalStateException("单例实例已存在!");
}
}
// 双重检查锁实现...
}
2. 序列化/反序列化保护
实现readResolve()方法保证反序列化返回同一实例
public class SerializableSingleton implements Serializable {
...
// 反序列化时替换为现有实例
private Object readResolve() {
return getInstance();
}
}
3. 多类加载器环境下的隐患
当存在多个类加载器时,每个加载器可能创建独立实例。解决方案:
ClassLoader.getSystemClassLoader().loadClass("SingletonClass");
四、五大黄金使用原则
- 首选枚举实现——除非需要继承父类
- 延迟加载需求→静态内部类
- 启动即用资源→饿汉式
- 避免DCL陷阱——必须配合volatile
- 禁用synchronized方法——性能影响过大
4
8
五、经典面试难题解析
Q1:为什么需要两次判空检查?
第一次检查:避免不必要的锁竞争(已初始化时直接返回)
第二次检查:防止并发创建(多个线程通过第一关后,在锁内二次确认)
Q2:volatile在DCL中的作用?
- 禁止指令重排序:阻止
new Singleton()操作被拆分为:
①分配内存 → ②返回引用 → ③初始化对象(正常顺序应为①→③→②)- 保证可见性:确保所有线程看到完全初始化的对象
Q3:枚举如何防止反射攻击?
枚举类型在JVM层禁止反射调用构造器,
Constructor#newInstance()方法内部会显式检查是否为枚举,若是则抛出IllegalArgumentException
单例模式的选择决策树:

最后忠告:在Spring等IoC容器环境中,避免滥用单例模式——容器管理的单例Bean已提供线程安全机制,手动实现反而导致依赖混乱

3616

被折叠的 条评论
为什么被折叠?



