Appearance
单例模式(Singleton Pattern)
1. 官方定义
"Ensure a class only has one instance, and provide a global point of access to it."
—— 《Design Patterns: Elements of Reusable Object-Oriented Software》(GoF, 1994)
核心:确保一个类只有一个实例,并提供全局访问点。
2. 模式解释
核心思想
- 唯一性控制:通过私有构造器禁止外部直接实例化对象。
- 全局访问:通过静态方法(如
getInstance()
)提供唯一实例的访问入口。
优点
- 资源优化:避免重复创建耗资源对象(如数据库连接池)。
- 全局一致性:统一管理共享资源(如配置管理器、日志服务)。
- 简化访问:无需传递对象引用,直接通过类方法获取实例。
缺点
- 隐藏耦合:全局状态可能导致代码难以测试(如依赖单例的类无法隔离测试)。
- 多线程风险:需额外处理线程安全问题(如双重检查锁)。
- 违反单一职责原则:单例类同时承担自身业务和实例管理的职责。
3. 解决的问题
经典场景
- 全局配置管理
- 应用中需要唯一配置中心(如
AppConfig
读取配置文件)。
- 应用中需要唯一配置中心(如
- 共享资源访问
- 线程池、缓存、日志记录器等需全局唯一实例。
- 硬件资源控制
- 打印机后台服务、文件系统管理器等需独占访问的资源。
4. 实现注意事项
关键实现点
- 线程安全
- 多线程环境下需防止重复创建实例(如使用双重检查锁)。
- 延迟加载(Lazy Initialization)
- 仅在首次访问时创建实例,避免启动时资源浪费。
- 反序列化与反射攻击
- 防止通过反射或反序列化破坏单例(如枚举实现天然防御)。
- 性能权衡
- 同步锁可能引入性能开销(如饿汉式 vs 懒汉式)。
代码示例(Java)
java
// 双重检查锁实现(线程安全 + 延迟加载)
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;
}
}
5. 实现变体
四种常见实现方式
类型 | 特点 | 适用场景 |
---|---|---|
饿汉式 | 类加载时立即初始化实例(线程安全,但可能浪费资源) | 实例占用资源小,启动即需使用 |
懒汉式(锁) | 首次访问时初始化,需同步锁保证线程安全(性能较低) | 不推荐使用 |
双重检查锁 | 延迟加载 + 线程安全(需 volatile 关键字防止指令重排) | 高并发场景 |
静态内部类 | 利用类加载机制保证线程安全,延迟加载(推荐方式) | 通用场景 |
枚举单例 | 天然防反射和反序列化攻击(《Effective Java》推荐方式) | 需严格防御破坏的场景 |
6. 相似模式对比
模式 | 核心区别 |
---|---|
工厂模式 | 关注对象创建过程,而单例模式关注对象唯一性 |
享元模式 | 管理多个可共享的相似对象,而单例模式管理唯一对象 |
全局变量 | 单例模式通过封装控制实例化过程,避免全局变量的不可控性和命名污染 |
7. 组合使用场景
常见搭配模式
- 工厂模式
- 场景:工厂类本身需要全局唯一(如数据库连接工厂)。
- 组合方式:将工厂类实现为单例。
- 代理模式
- 场景:为单例对象增加访问控制(如日志记录器的权限校验)。
- 抽象工厂模式
- 场景:抽象工厂的具体实现类需保证唯一性(如跨平台 UI 工厂)。
8. 总结记忆点
- 核心价值:确保全局唯一性,简化共享资源管理。
- 线程安全优先级:枚举 > 静态内部类 > 双重检查锁 > 懒汉式锁。
- 反模式警示:滥用单例会导致代码耦合度高、难以测试(优先考虑依赖注入)。
- 适用性判断:当且仅当系统中确需严格唯一实例时使用(如硬件控制)。
附:单例模式的误用风险
- 测试困难:单例的全局状态可能导致单元测试互相干扰(需通过依赖注入解耦)。
- 内存泄漏:长时间持有的单例可能阻止资源回收(如 Android 中 Context 单例)。
- 扩展性差:若未来需支持多实例,需重构大量代码(优先通过工厂模式预留扩展点)。