单例模式
1/8/24About 5 min
单例模式,确保一个类只有一个实例,并且提供了对该类的全局访问入口,它可以确保使用这个类实例的所有对象都使用相同的实例。

单例模式-懒汉模式
在需要的时候才创建实例,并且只创建一次。
public class SingletonA {
private static SingletonA instance = null;
public SingletonA() {
}
public static SingletonA getInstance() {
if(instance == null){
instance = new SingletonA();
}
return instance;
}
}单例模式-饿汉模式
在类加载的时候就创建实例。
public class SingletonB {
private final static SingletonB instance = new SingletonB();
public static SingletonB getInstance() {
return instance;
}
}测试
执行main方法,结果输出均为true。
public class Client {
public static void main(String[] args) {
AbstractProduct productA = SimpleFactory.createProduct("A");
productA.work();
AbstractProduct productB = SimpleFactory.createProduct("B");
productB.work();
}
}改进
如果考虑并发的情形,上面的懒汉模式会创建多个实例,因此需要加锁。
public class SyncSingletonA {
private static volatile SyncSingletonA instance = null;
public SyncSingletonA() {
}
public static SyncSingletonA getInstance() {
if (instance == null) {
synchronized (SyncSingletonA.class) {
if (instance == null) {
instance = new SyncSingletonA();
}
}
}
return instance;
}
}加锁之后,还要进行判空,目的是为了防止其他线程创建类实例后,当前线程又再次创建。
与其他设计模式的对比
单例模式 vs 工厂方法模式
| 特性 | 单例模式 | 工厂方法模式 |
|---|---|---|
| 目的 | 确保类只有一个实例 | 创建不同类型的产品 |
| 实例数量 | 只能有一个实例 | 可以创建多个实例 |
| 全局访问 | 提供全局访问点 | 不提供全局访问点 |
| 适用场景 | 需唯一实例的场景 | 产品类型多样的场景 |
| 扩展性 | 低(只能是一个实例) | 高(可扩展新的产品) |
单例模式 vs 抽象工厂模式
| 特性 | 单例模式 | 抽象工厂模式 |
|---|---|---|
| 产品范围 | 单个类的唯一实例 | 产品家族的创建 |
| 实例数量 | 只能有一个实例 | 可以创建多个实例 |
| 全局访问 | 提供全局访问点 | 不提供全局访问点 |
| 灵活性 | 低(只能是一个实例) | 中(可扩展产品家族) |
| 适用场景 | 需唯一实例的场景 | 需创建相关对象家族的场景 |
单例模式 vs 建造者模式
| 特性 | 单例模式 | 建造者模式 |
|---|---|---|
| 目的 | 确保类只有一个实例 | 构建复杂对象的不同表示 |
| 构建方式 | 简单的实例化或延迟加载 | 分步构建过程 |
| 实例数量 | 只能有一个实例 | 可以创建多个实例 |
| 客户端参与度 | 低(只需调用getInstance) | 高(需指导构建过程) |
| 适用场景 | 需唯一实例的场景 | 需构建复杂对象的场景 |
单例模式 vs 原型模式
| 特性 | 单例模式 | 原型模式 |
|---|---|---|
| 目的 | 确保类只有一个实例 | 通过复制现有对象创建新对象 |
| 实例数量 | 只能有一个实例 | 可以创建多个实例 |
| 创建方式 | 实例化或延迟加载 | 复制现有对象 |
| 性能 | 一般(首次访问需要创建实例) | 高(避免了重复初始化) |
| 适用场景 | 需唯一实例的场景 | 需频繁创建相似对象的场景 |
总结
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。
核心思想
单例模式的核心思想是通过限制类的实例化过程,确保在整个应用程序生命周期中,某个类只有一个实例存在,并提供一个全局访问点来获取这个实例。
主要实现方式
饿汉模式
- 优点:实现简单,线程安全(类加载时初始化)
- 缺点:可能导致不必要的资源消耗(即使不使用也会创建实例)
- 适用场景:单例对象较小,创建成本低
懒汉模式
- 优点:延迟加载,节省资源
- 缺点:非线程安全(需要额外处理线程安全问题)
- 适用场景:单例对象较大,创建成本高
线程安全的懒汉模式(双重检查锁定)
- 优点:延迟加载,线程安全,性能较好
- 缺点:实现复杂,需要使用volatile关键字防止指令重排序
- 适用场景:需要线程安全且延迟加载的场景
静态内部类模式
- 优点:延迟加载,线程安全(JVM类加载机制保证)
- 缺点:实现稍复杂
- 适用场景:需要线程安全且延迟加载的场景
枚举模式
- 优点:实现简单,线程安全,防止反射和序列化攻击
- 缺点:无法延迟加载
- 适用场景:需要绝对线程安全和防止反射/序列化攻击的场景
优点
- 控制实例数量:确保类只有一个实例,避免了资源浪费
- 全局访问点:提供了一个统一的访问点,方便对实例的管理
- 避免重复创建:减少了对象的创建次数,提高了性能
- 简化配置管理:适合管理全局配置、日志记录器等
缺点
- 违反单一职责原则:单例类既要负责业务逻辑,又要负责实例管理
- 隐藏依赖关系:通过全局访问点获取实例,可能导致依赖关系不明确
- 测试困难:单例模式可能导致测试困难,特别是在需要模拟对象的情况下
- 扩展性差:单例类的扩展性受到限制,难以继承和扩展
适用场景
- 资源共享场景:如数据库连接池、线程池等需要共享资源的场景
- 配置管理:如全局配置信息、系统参数等
- 日志记录:应用程序中的日志记录器
- 工具类:如数学工具类、日期工具类等不需要多个实例的场景
- 硬件访问:如打印机、摄像头等硬件设备的访问控制
注意事项
- 要考虑线程安全问题,特别是在多线程环境下
- 要考虑序列化和反序列化对单例模式的影响
- 要考虑反射对单例模式的影响
- 谨慎使用单例模式,避免过度使用导致代码难以测试和维护