0%

单例模式

单例模式(Singleton Pattern)详解

单例模式是创建型设计模式中最常用的模式之一,其核心是保证一个类在系统中仅有一个实例,并提供全局访问点。这种模式适用于需要统一控制资源、避免重复创建对象的场景(如配置管理器、日志工具等)。

单例模式的核心原则

实现单例模式需遵循四大原则:

  1. 构造方法私有:防止外部通过new关键字创建实例。
  2. 以静态方法或枚举返回实例:提供全局访问接口。
  3. 确保实例唯一:通过设计避免多线程或其他场景下的重复创建。
  4. 防止反序列化重复实例:确保对象序列化 / 反序列化后仍为单例。

单例模式的两种核心实现方式

根据实例化时机的不同,单例模式分为饿汉式(立即加载)和懒汉式(延迟加载)。

1. 饿汉式(Eager Initialization)

核心思想

类加载时立即创建实例,通过类加载机制保证线程安全。

实现代码
1
2
3
4
5
6
7
8
9
10
11
12
public class EagerSingleton {
// 类加载时直接初始化实例
private static final EagerSingleton instance = new EagerSingleton();

// 私有构造方法,禁止外部实例化
private EagerSingleton() {}

// 全局访问点
public static EagerSingleton getInstance() {
return instance;
}
}
特点
  • 线程安全:类加载过程由 JVM 保证线程安全,仅初始化一次。
  • 立即加载:类加载时就创建实例,无论是否使用,可能浪费内存。
  • 优点:实现简单,无需考虑线程同步问题。
  • 缺点:如果实例占用资源大且长时间不使用,会造成资源浪费。

2. 懒汉式(Lazy Initialization)

核心思想

首次使用时才创建实例,节省资源(时间换空间),但需处理线程安全问题。

(1)基础版(非线程安全)
1
2
3
4
5
6
7
8
9
10
11
12
13
public class LazySingleton {
private static LazySingleton instance;

private LazySingleton() {}

public static LazySingleton getInstance() {
// 首次调用时创建实例
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
  • 问题:多线程并发调用getInstance()时,可能创建多个实例。
(2)同步方法版(线程安全,效率低)
1
2
3
4
5
6
7
8
9
10
11
12
13
public class SyncLazySingleton {
private static SyncLazySingleton instance;

private SyncLazySingleton() {}

// 对方法加锁,保证线程安全
public static synchronized SyncLazySingleton getInstance() {
if (instance == null) {
instance = new SyncLazySingleton();
}
return instance;
}
}
  • 线程安全synchronized修饰方法,防止并发创建。
  • 缺点:每次调用getInstance()都需要加锁,性能开销大。
(3)双重检查锁(DCL,高效线程安全)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class DCLSingleton {
// volatile防止指令重排导致的空指针问题
private static volatile DCLSingleton instance;

private DCLSingleton() {}

public static DCLSingleton getInstance() {
// 第一次检查:避免已创建实例后仍进入同步块
if (instance == null) {
// 同步块:限制仅一个线程进入实例化逻辑
synchronized (DCLSingleton.class) {
// 第二次检查:防止多线程等待锁时重复创建
if (instance == null) {
instance = new DCLSingleton();
}
}
}
return instance;
}
}
  • 核心优化:
    • 外层if:避免实例创建后仍执行同步操作,提高效率。
    • 内层if:防止多个线程同时进入同步块后重复创建实例。
    • volatile:禁止instance = new DCLSingleton()的指令重排(避免其他线程获取到未初始化完成的实例)。
  • 特点:线程安全且性能优异,是懒汉式的推荐实现。
(4)静态内部类版(线程安全,延迟加载)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class StaticInnerClassSingleton {
// 静态内部类:仅在调用getInstance()时加载
private static class SingletonHolder {
// 内部类中创建实例
private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
}

private StaticInnerClassSingleton() {}

public static StaticInnerClassSingleton getInstance() {
// 触发内部类加载,创建实例
return SingletonHolder.INSTANCE;
}
}
  • 线程安全:内部类加载由 JVM 保证线程安全,仅初始化一次。
  • 延迟加载:内部类不会随外部类加载而初始化,仅在首次调用getInstance()时加载。
  • 优点:实现简单,无需手动处理线程同步,效率高。
(5)枚举单例(防反射、防序列化)

上述实现均存在反射攻击(通过反射调用私有构造)和序列化问题(反序列化生成新实例),枚举单例可解决这些问题:

1
2
3
4
5
6
7
8
9
public enum EnumSingleton {
// 枚举实例(唯一)
INSTANCE;

// 自定义方法
public void doSomething() {
System.out.println("枚举单例执行操作");
}
}
  • 使用方式EnumSingleton.INSTANCE.doSomething();
  • 优势:
    • 天然线程安全:枚举实例在类加载时创建,由 JVM 保证唯一性。
    • 防反射:JVM 禁止反射创建枚举实例。
    • 防序列化:枚举的序列化机制会保证反序列化后仍是原实例。
  • 缺点:无法延迟加载(枚举实例在类加载时创建)。

单例模式的特点与适用场景

核心特点

  • 单例类仅有一个实例。
  • 单例类自行创建唯一实例。
  • 提供全局访问点(如getInstance())。

优点

  • 节省资源:避免重复创建销毁对象,减少内存占用。
  • 提高性能:频繁使用的对象(如数据库连接池)可减少初始化开销。
  • 全局控制:便于统一管理共享资源(如配置信息、日志输出)。

缺点

  • 扩展困难:单例类通常通过静态方法访问,无法通过继承扩展。
  • 耦合性高:全局访问点可能导致代码依赖紧密,不利于测试。
  • 潜在内存泄漏:单例生命周期与应用一致,若持有大量资源可能导致泄漏。

适用场景

  • 资源共享场景:如日志工具、配置管理器、线程池。
  • 控制资源访问场景:如数据库连接、文件读写(避免并发冲突)。
  • 频繁创建销毁会消耗大量资源的对象:如重量级服务实例。

单例模式的注意事项

  1. 线程安全:懒汉式必须处理多线程并发问题(推荐 DCL 或静态内部类)。
  2. 反射与序列化:普通单例需额外处理(如添加readResolve()方法防止反序列化重复),枚举单例天然免疫。
  3. 内存管理:避免单例持有过多资源或长生命周期对象,防止内存泄漏。
  4. 测试难度:单例依赖全局状态,可能增加单元测试复杂度(可通过依赖注入缓解)。

总结

单例模式通过控制实例唯一性,在资源管理和全局访问场景中发挥重要作用。选择实现方式时需权衡:

  • 若需简单安全且可接受立即加载:饿汉式或枚举单例。
  • 若需延迟加载且注重性能:双重检查锁(DCL)或静态内部类。
  • 若需防反射和序列化:优先选择枚举单例

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10