单例模式(Singleton Pattern)详解
单例模式是创建型设计模式中最常用的模式之一,其核心是保证一个类在系统中仅有一个实例,并提供全局访问点。这种模式适用于需要统一控制资源、避免重复创建对象的场景(如配置管理器、日志工具等)。
单例模式的核心原则
实现单例模式需遵循四大原则:
- 构造方法私有:防止外部通过
new关键字创建实例。 - 以静态方法或枚举返回实例:提供全局访问接口。
- 确保实例唯一:通过设计避免多线程或其他场景下的重复创建。
- 防止反序列化重复实例:确保对象序列化 / 反序列化后仍为单例。
单例模式的两种核心实现方式
根据实例化时机的不同,单例模式分为饿汉式(立即加载)和懒汉式(延迟加载)。
1. 饿汉式(Eager Initialization)
核心思想
类加载时立即创建实例,通过类加载机制保证线程安全。
实现代码
1 | public class EagerSingleton { |
特点
- 线程安全:类加载过程由 JVM 保证线程安全,仅初始化一次。
- 立即加载:类加载时就创建实例,无论是否使用,可能浪费内存。
- 优点:实现简单,无需考虑线程同步问题。
- 缺点:如果实例占用资源大且长时间不使用,会造成资源浪费。
2. 懒汉式(Lazy Initialization)
核心思想
首次使用时才创建实例,节省资源(时间换空间),但需处理线程安全问题。
(1)基础版(非线程安全)
1 | public class LazySingleton { |
- 问题:多线程并发调用
getInstance()时,可能创建多个实例。
(2)同步方法版(线程安全,效率低)
1 | public class SyncLazySingleton { |
- 线程安全:
synchronized修饰方法,防止并发创建。 - 缺点:每次调用
getInstance()都需要加锁,性能开销大。
(3)双重检查锁(DCL,高效线程安全)
1 | public class DCLSingleton { |
- 核心优化:
- 外层
if:避免实例创建后仍执行同步操作,提高效率。 - 内层
if:防止多个线程同时进入同步块后重复创建实例。 volatile:禁止instance = new DCLSingleton()的指令重排(避免其他线程获取到未初始化完成的实例)。
- 外层
- 特点:线程安全且性能优异,是懒汉式的推荐实现。
(4)静态内部类版(线程安全,延迟加载)
1 | public class StaticInnerClassSingleton { |
- 线程安全:内部类加载由 JVM 保证线程安全,仅初始化一次。
- 延迟加载:内部类不会随外部类加载而初始化,仅在首次调用
getInstance()时加载。 - 优点:实现简单,无需手动处理线程同步,效率高。
(5)枚举单例(防反射、防序列化)
上述实现均存在反射攻击(通过反射调用私有构造)和序列化问题(反序列化生成新实例),枚举单例可解决这些问题:
1 | public enum EnumSingleton { |
- 使用方式:
EnumSingleton.INSTANCE.doSomething(); - 优势:
- 天然线程安全:枚举实例在类加载时创建,由 JVM 保证唯一性。
- 防反射:JVM 禁止反射创建枚举实例。
- 防序列化:枚举的序列化机制会保证反序列化后仍是原实例。
- 缺点:无法延迟加载(枚举实例在类加载时创建)。
单例模式的特点与适用场景
核心特点
- 单例类仅有一个实例。
- 单例类自行创建唯一实例。
- 提供全局访问点(如
getInstance())。
优点
- 节省资源:避免重复创建销毁对象,减少内存占用。
- 提高性能:频繁使用的对象(如数据库连接池)可减少初始化开销。
- 全局控制:便于统一管理共享资源(如配置信息、日志输出)。
缺点
- 扩展困难:单例类通常通过静态方法访问,无法通过继承扩展。
- 耦合性高:全局访问点可能导致代码依赖紧密,不利于测试。
- 潜在内存泄漏:单例生命周期与应用一致,若持有大量资源可能导致泄漏。
适用场景
- 资源共享场景:如日志工具、配置管理器、线程池。
- 控制资源访问场景:如数据库连接、文件读写(避免并发冲突)。
- 频繁创建销毁会消耗大量资源的对象:如重量级服务实例。
单例模式的注意事项
- 线程安全:懒汉式必须处理多线程并发问题(推荐 DCL 或静态内部类)。
- 反射与序列化:普通单例需额外处理(如添加
readResolve()方法防止反序列化重复),枚举单例天然免疫。 - 内存管理:避免单例持有过多资源或长生命周期对象,防止内存泄漏。
- 测试难度:单例依赖全局状态,可能增加单元测试复杂度(可通过依赖注入缓解)。
总结
单例模式通过控制实例唯一性,在资源管理和全局访问场景中发挥重要作用。选择实现方式时需权衡:
- 若需简单安全且可接受立即加载:饿汉式或枚举单例。
- 若需延迟加载且注重性能:双重检查锁(DCL)或静态内部类。
- 若需防反射和序列化:优先选择枚举单例
v1.3.10