单例模式:确保唯一实例的设计模式
一、模式核心:保证类仅有一个实例并提供全局访问点
在软件开发中,有些类需要确保只有一个实例(如系统配置类、日志管理器),避免因多个实例导致状态混乱或资源浪费。
单例模式(Singleton Pattern) 通过私有化构造方法、持有唯一实例引用、提供静态访问接口,确保一个类在全局范围内只有一个实例,并提供统一的访问入口。核心解决:
- 实例唯一性:避免创建多个实例消耗资源(如数据库连接池、线程池)。
- 全局可访问性:为全局提供一个访问点,简化客户端调用。
- 延迟初始化:支持实例的延迟加载(按需创建),提升系统性能。
核心思想与 UML 类图
单例模式的核心是私有化构造方法 ,并通过静态方法返回唯一实例。常见实现方式包括:
- 饿汉式(类加载时立即创建实例)
- 懒汉式(第一次调用时创建实例,需处理线程安全)

二、核心实现:三种经典单例模式
1. 饿汉式单例(线程安全,类加载时创建实例)
java
public class EagerSingleton {
// 类加载时立即创建实例(静态变量初始化)
private static final EagerSingleton instance = new EagerSingleton();
// 私有化构造方法,防止外部实例化
private EagerSingleton() {
System.out.println("创建饿汉式单例实例");
}
// 公共访问方法,直接返回实例
public static EagerSingleton getInstance() {
return instance;
}
}
特点:
- 优点:简单可靠,类加载时完成初始化,天然线程安全。
- 缺点:无论是否使用都会创建实例,可能浪费内存(适用于实例创建成本低的场景)。
2. 懒汉式单例(线程不安全,延迟创建实例)
java
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
System.out.println("创建懒汉式单例实例");
}
// 未加锁,多线程环境可能创建多个实例
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
特点:
- 优点:延迟加载,节省内存。
- 缺点:多线程环境下不安全,可能出现多个实例(需改进为线程安全版本)。
3. 线程安全的懒汉式(双重检查锁定,DCL)
java
public class ThreadSafeSingleton {
private static volatile ThreadSafeSingleton instance; // volatile 禁止指令重排
private ThreadSafeSingleton() {
System.out.println("创建线程安全懒汉式单例实例");
}
public static ThreadSafeSingleton getInstance() {
// 第一次检查:实例是否已创建
if (instance == null) {
synchronized (ThreadSafeSingleton.class) { // 同步块,保证线程安全
// 第二次检查:防止多个线程同时通过第一次检查
if (instance == null) {
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
}
关键细节:
volatile
关键字:确保instance
的可见性和禁止指令重排,避免初始化未完成时被其他线程访问。- 双重检查(Double-Check Locking):减少同步块的竞争,提升性能。
三、进阶:使用枚举实现单例(推荐方式)
Java 枚举天然支持单例模式,且简洁可靠,自动处理序列化和反射攻击问题。
java
public enum EnumSingleton {
INSTANCE; // 唯一实例
// 附加方法示例
public void doSomething() {
System.out.println("枚举单例执行操作");
}
}
调用方式:
java
EnumSingleton.INSTANCE.doSomething(); // 直接通过枚举成员访问
优点:
- 简洁高效,无需手动处理线程安全和序列化问题。
- 防止通过反射创建新实例(
Enum
类禁止反射攻击)。
四、框架与源码中的单例实践
1. Spring 框架中的单例 Bean
Spring 默认创建的 Bean 是单例的,通过 BeanFactory
管理实例的唯一性。
java
@Service
public class UserService {
// Spring 自动创建单例实例
}
2. Log4j 日志管理器
Log4j 的 Logger
类使用单例模式,确保每个类对应的日志记录器唯一。
java
public class App {
private static final Logger logger = Logger.getLogger(App.class);
public static void main(String[] args) {
logger.info("单例日志记录器");
}
}
五、避坑指南:正确使用单例模式的 4 个要点
1. 处理序列化与反序列化攻击
若单例类实现了 Serializable
接口,需添加 readResolve()
方法防止反序列化创建新实例:
java
protected Object readResolve() {
return instance; // 返回现有实例,避免创建新对象
}
2. 防止反射攻击
通过在构造方法中添加校验,禁止通过反射创建多个实例:
java
private Singleton() {
if (instance != null) {
throw new IllegalStateException("单例实例已存在");
}
// 初始化逻辑
}
3. 避免单例持有长生命周期对象
单例若持有大对象或上下文(如 ApplicationContext
),可能导致内存泄漏,需及时释放资源。
4. 谨慎使用延迟加载
懒汉式单例需确保线程安全,否则可能引发 bug;若实例创建成本低,优先使用饿汉式或枚举式。
六、总结:何时该用单例模式?
适用场景 | 核心特征 | 典型案例 |
---|---|---|
全局唯一配置 | 配置信息需要全局共享且唯一 | 系统配置类(ConfigManager) |
资源池管理 | 控制资源(如数据库连接)的创建数量 | 数据库连接池、线程池 |
日志记录器 | 全局共享日志实例 | Log4j、Logback |
避免重复初始化 | 初始化成本高,需保证仅执行一次 | 重量级对象(如缓存管理器) |
单例模式通过严格控制实例数量,实现了全局状态的统一管理。下一篇我们将探讨建造者模式,解析如何分步构建复杂对象,敬请期待!
扩展思考:单例模式的缺点
- 测试困难:单例与测试框架(如 JUnit)的依赖注入冲突,需通过模拟或反射绕过。
- 违背单一职责原则:单例可能承担业务逻辑与实例管理双重职责,建议将实例管理抽象为独立工厂。