单例模式(Singleton Pattern)
单例模式是最常用的设计模式之一,用于确保一个类只有一个实例,并提供全局访问点。
1. 应用场景
- 配置管理:全局配置信息只需要一个实例
- 日志记录器:整个应用共享一个日志实例
- 数据库连接池:管理数据库连接资源
- 线程池:统一管理线程资源
- 缓存:全局缓存管理
2. 实现方式
2.1 饿汉式(线程安全)
类加载时就创建实例,简单但可能浪费资源。
java
public class Singleton {
// 类加载时就创建实例
private static final Singleton INSTANCE = new Singleton();
// 私有构造函数
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}优点:
- 线程安全(JVM 类加载机制保证)
- 实现简单
缺点:
- 类加载就创建,可能造成资源浪费
- 无法传递参数
2.2 懒汉式(非线程安全)
延迟加载,但多线程下不安全。
java
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton(); // 多线程下可能创建多个实例
}
return instance;
}
}⚠️ 警告:这种写法在多线程环境下是不安全的,不推荐使用!
2.3 懒汉式 + synchronized(线程安全但低效)
java
public class Singleton {
private static Singleton instance;
private Singleton() {}
// 整个方法加锁,性能差
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}缺点:每次调用都要获取锁,性能开销大。
2.4 双重检查锁(DCL,推荐)⭐
java
public class Singleton {
// 必须使用 volatile 防止指令重排序
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;
}
}为什么需要 volatile?
instance = new Singleton() 这行代码实际上分三步执行:
- 分配内存空间
- 初始化对象
- 将 instance 指向分配的内存
由于 JVM 指令重排序,可能变成 1 → 3 → 2,导致其他线程获取到未初始化完成的对象。
volatile 的作用:
- 禁止指令重排序
- 保证可见性
2.5 静态内部类(推荐)⭐
java
public class Singleton {
private Singleton() {}
// 静态内部类
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}优点:
- 延迟加载:外部类加载时,内部类不会加载
- 线程安全:JVM 类加载机制保证
- 无锁:性能优秀
原理:
SingletonHolder只有在getInstance()被调用时才会加载- 类加载过程是线程安全的(由 JVM 保证)
2.6 枚举(最佳实践)⭐⭐⭐
java
public enum Singleton {
INSTANCE;
// 可以添加方法
public void doSomething() {
System.out.println("执行操作");
}
}
// 使用
Singleton.INSTANCE.doSomething();优点:
- 绝对线程安全:JVM 保证枚举实例唯一
- 防止反序列化重新创建对象
- 防止反射攻击
- 代码简洁
《Effective Java》推荐:单元素的枚举类型是实现单例的最佳方法。
3. 各实现方式对比
| 实现方式 | 线程安全 | 延迟加载 | 防反射 | 防反序列化 | 推荐度 |
|---|---|---|---|---|---|
| 饿汉式 | ✅ | ❌ | ❌ | ❌ | ⭐⭐ |
| 懒汉式(无锁) | ❌ | ✅ | ❌ | ❌ | ❌ |
| 懒汉式(synchronized) | ✅ | ✅ | ❌ | ❌ | ⭐ |
| 双重检查锁(DCL) | ✅ | ✅ | ❌ | ❌ | ⭐⭐⭐ |
| 静态内部类 | ✅ | ✅ | ❌ | ❌ | ⭐⭐⭐ |
| 枚举 | ✅ | ❌ | ✅ | ✅ | ⭐⭐⭐⭐⭐ |
4. 防止反射攻击
普通单例可以被反射破坏:
java
// 反射破坏单例
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance2 = constructor.newInstance();
System.out.println(Singleton.getInstance() == instance2); // false防护措施:
java
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
// 防止反射攻击
if (instance != null) {
throw new RuntimeException("单例已存在,禁止反射创建!");
}
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}5. 防止反序列化破坏
java
public class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
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;
}
// 防止反序列化创建新对象
private Object readResolve() {
return instance;
}
}6. Spring 中的单例
Spring 框架默认 Bean 是单例的(scope="singleton"):
java
@Component
public class UserService {
// Spring 容器管理,默认单例
}
@Configuration
public class AppConfig {
@Bean
@Scope("singleton") // 默认值,可省略
public UserService userService() {
return new UserService();
}
}Spring 单例 vs 设计模式单例:
| 特性 | Spring 单例 | 设计模式单例 |
|---|---|---|
| 作用范围 | Spring 容器内唯一 | JVM 内唯一 |
| 创建方式 | IoC 容器管理 | 类自身控制 |
| 生命周期 | 容器管理 | 自行管理 |
7. 最佳实践
- 优先使用枚举:最安全、最简洁
- 静态内部类次之:延迟加载、无锁
- DCL 也可以:需确保 volatile 关键字
- 避免使用懒汉式无锁版本:线程不安全
- 在 Spring 中:直接使用容器管理的单例 Bean