单例设计模式(Singleton Design Pattern)
1 概念
一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
2 实现
实现单例时,需要注意:
- 构造函数需要是private访问权限的,这样才能避免外部通过new创建实例
- 考虑对象创建时的线程安全问题
- 考虑获取对象实例(getInstance())时的性能问题
- 考虑是否支持延迟加载
单例模式包含以下实现方式:
- 懒汉式(延迟加载、并发度低)
- 饿汉式(非延迟加载、并发度高)
- 双重校验(延迟加载、并发度高、手动保证线程安全)
- 静态内部类(延迟加载、并发度高、无需手动保证线程安全)
- 枚举类(延迟加载、并发度高、无需手动保证线程安全、最简单的实现方式)
2.1 懒汉式
使用时,再初始化instance实例
优点:
- 支持延迟加载
缺点:
- 获取实例时需要加锁,并发度低
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton(){}
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
2.2 饿汉式
类加载时,instance实例就已经创建并初始化了
优点:
- 获取实例时不需要加锁,并发度高
缺点:
- 不支持延迟加载,instance实例可能一直都不会被用到,浪费资源
public class HungrySingleton {
private final static HungrySingleton instance = new HungrySingleton();
private HungrySingleton (){}
public static HungrySingleton getInstance() {
return instance;
}
}
2.3 双重校验
支持延迟加载,避免每次获取实例都要加锁
优点:
- 支持延迟加载
- 获取实例时不需要加锁,并发度高
缺点:
- 需要手动保证线程安全(synchronized)
public class DoubleCheckSingleton {
// private volatile static DoubleCheckSingleton instance; // 低版本java
private static DoubleCheckSingleton instance;// 高版本java
private DoubleCheckSingleton (){}
public static DoubleCheckSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckSingleton.class) {
if (instance == null) {
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
备注(关于volatile关键字的使用):
-
低版本的 Java 因为指令重排序,可能会导致 DoubleCheckSingleton 对象被 new 出来,并且赋值给 instance 之后,还没来得及初始化(执行构造函数中的代码逻辑),就被另一个线程使用了(另一个线程使用时因为实例还没有被初始化完成,从而会抛出异常)。要解决这个问题,需要给 instance 成员变量加上 volatile 关键字,禁止指令重排序才行
-
高版本的 Java 已经在 JDK 内部实现中解决了这个问题(解决的方法很简单,只要把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序)
2.4 静态内部类
创建类时,不初始化instance实例;当调用getInstance()方法时,才会去初始化
优点:
- 支持延迟加载
- 获取实例时不需要加锁,并发度高
- 不需要手动保证线程安全,由jvm保证
public class StaticInnerClassSingleton {
private static StaticInnerClassSingleton instance;
public static class InstanceHolder {
private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return InstanceHolder.instance;
}
}
2.5 枚举
最简单的实现方案
优点:
- 通过 Java 枚举类型本身的特性,保证了线程安全和实例唯一
缺点:
- JDK1.5及以上版本才加入enum特性
public enum EnumSingleton {
INSTANCE;
}
3 思考
3.1 单例的使用场景
- 处理资源访问冲突
- 日志类(非单例情况下,多线程往同一个文件写日志可能存在互相覆盖的情况)
- 表示全局唯一类
- ID生成器
- 配置类
- 连接池类
3.2 单例存在的问题
- 单例对 OOP 特性的支持不友好
- 单例会隐藏类之间的依赖关系
- 单例对代码的扩展性不友好
- 单例对代码的可测试性不友好
- 单例不支持有参数的构造函数
3.3 有何替代方案
- 工厂模式
- IOC 容器
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~