设计模式实战篇(一):彻底搞懂 Singleton 单例模式
💬 关键词:创建型设计模式、线程安全、JVM 类加载机制、反射防护、Spring 单例
一、什么是单例模式?
单例模式(Singleton Pattern) 是一种最经典的“创建型”设计模式,确保在整个系统生命周期中,某个类只有一个实例,并为全局提供访问点。
💡 举例:
- 一个系统只有一个配置管理器。
- 日志模块全局共享一个 Logger。
- 线程池、数据库连接池等都是单例。
二、核心设计原则
| 原则 | 含义 |
|---|---|
| 构造器私有化 | 防止外部随意实例化 |
| 静态变量持有实例 | 确保全局唯一 |
| 静态方法提供访问点 | 控制访问 |
| 线程安全控制 | 多线程下仍然唯一 |
三、UML 类图
┌────────────────────────┐
│ Singleton │
├────────────────────────┤
│ - instance : Singleton │
├────────────────────────┤
│ + getInstance() │
└────────────────────────┘
四、单例的五种实现方式对比
| 实现方式 | 是否懒加载 | 是否线程安全 | 性能 | 是否推荐 | 备注 |
|---|---|---|---|---|---|
| 饿汉式 | ❌ | ✅ | 👍 | ⚠️ 否 | 占用资源 |
| 懒汉式 | ✅ | ❌ | 👎 | ❌ 否 | 非线程安全 |
| 双重检查锁(DCL) | ✅ | ✅ | 👍👍 | ✅ | 推荐使用 |
| 静态内部类 | ✅ | ✅ | 👍👍👍 | 🌟 推荐 | 简洁优雅 |
| 枚举单例 | ❌ | ✅ | 👍👍👍 | 🌟🌟 强烈推荐 | 最安全方案 |
五、代码示例
1️⃣ 饿汉式
public class SingletonEager {private static final SingletonEager INSTANCE = new SingletonEager();private SingletonEager() {}public static SingletonEager getInstance() {return INSTANCE;}
}
特点:
-
类加载时就创建实例。
-
天然线程安全,但浪费内存。
2️⃣ 懒汉式(Lazy Initialization)- 非线程安全
public class SingletonLazy {private static SingletonLazy instance;private SingletonLazy() {}public static SingletonLazy getInstance() {if (instance == null) {instance = new SingletonLazy();}return instance;}
}
问题:
多个线程同时进入 if (instance == null),可能创建多个实例。
3️⃣ 双重检查锁(DCL)
public class SingletonDCL {private static volatile SingletonDCL instance;private SingletonDCL() {}public static SingletonDCL getInstance() {if (instance == null) {synchronized (SingletonDCL.class) {if (instance == null) {instance = new SingletonDCL();}}}return instance;}
}
为什么需要 volatile?
对象实例化可能被编译器重排序:
1️⃣ 分配内存
2️⃣ 调用构造函数
3️⃣ 引用指向内存地址
若 2️⃣ 与 3️⃣ 调换顺序 → 其他线程可能拿到一个“未完全初始化”的对象。
volatile 关键字禁止这种指令重排。
原理解析:
-
volatile保证禁止指令重排。 -
双层检查保证性能与安全。
4️⃣ 静态内部类(推荐实现)
public class SingletonInner {private SingletonInner() {}private static class Holder {private static final SingletonInner INSTANCE = new SingletonInner();}public static SingletonInner getInstance() {return Holder.INSTANCE;}
}
JVM 原理:
Holder类不会在外部类加载时立即加载。- 当调用
getInstance()时才加载Holder。 - JVM 保证类加载过程的线程安全性。
5️⃣ 枚举单例(最优雅实现)
public enum SingletonEnum {INSTANCE;public void doSomething() {System.out.println("Enum Singleton working!");}
}
🧠 原理:
-
枚举类由 JVM 保证只加载一次。
-
天然防止反射与反序列化攻击。
六、线程安全分析
| 实现 | 是否线程安全 | 说明 |
|---|---|---|
| 饿汉式 | ✅ | 类加载时完成实例化 |
| 懒汉式 | ❌ | 多线程会创建多个实例 |
| DCL | ✅ | 结合 volatile 可安全高效 |
| 静态内部类 | ✅ | 类加载机制保证安全 |
| 枚举 | ✅ | JVM 保证枚举类实例唯一性 |
七、反射与序列化
1️⃣ 反射破坏单例
即使构造函数私有,也可通过反射调用创建多个实例。
解决办法:在构造函数中检测是否已有实例。
if (instance != null) {throw new RuntimeException("禁止通过反射创建对象!");
}
2️⃣ 序列化破坏单例
反序列化会生成新对象。
解决办法:添加 readResolve() 方法。
protected Object readResolve() {return getInstance();
}
八、应用场景
| 场景 | 示例 |
|---|---|
| 系统配置类 | ConfigManager |
| 日志管理 | Logger |
| 线程池 | ThreadPoolExecutor |
| 缓存 | RedisManager |
| 数据库连接池 | DataSource |
日志管理器(Logger)
public class LoggerManager {private LoggerManager() {}private static class Holder {private static final LoggerManager INSTANCE = new LoggerManager();}public static LoggerManager getInstance() {return Holder.INSTANCE;}public void log(String msg) {System.out.println("[LOG] " + msg);}
}
使用示例:
public class App {public static void main(String[] args) {LoggerManager logger = LoggerManager.getInstance();logger.log("Application started.");}
}
控制台输出:
[LOG] Application started.
九、优缺点
| 优点 | 缺点 |
|---|---|
| 全局唯一对象,节省资源 | 难以扩展,测试困难 |
| 统一访问点,便于管理 | 并发复杂度高 |
| 支持延迟加载(部分实现) | 潜在隐藏依赖 |
十、单例在 Spring 框架中的体现
| 场景 | 描述 |
|---|---|
| IOC 容器默认作用域 | Spring Bean 默认是单例(singleton) |
| Bean 生命周期管理 | 容器初始化时加载实例 |
| 线程安全性 | Spring 通过容器同步机制控制实例唯一性 |
📘 源码示例(AbstractBeanFactory.java)
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
十一、总结
| 实现方式 | 是否推荐 | 备注 |
|---|---|---|
| 饿汉式 | ❌ | 占用资源 |
| 懒汉式 | ❌ | 非线程安全 |
| 双检锁 | ✅ | 高性能、安全 |
| 静态内部类 | ✅ | 推荐方式 |
| 枚举单例 | ✅ | 最优雅实现 |
✅ 最佳实践:推荐使用“静态内部类”或“枚举单例”。
十一、单例模式的演进对比图(示意)
创建时机 ───────────────────────────►
饿汉式 ──┬─────────────► 立即创建
懒汉式 ──┬─────► 延迟创建(不安全)
DCL ─────┬─────► 延迟创建 + 锁优化
内部类 ──┬─────► JVM 加载机制保证线程安全
枚举 ────┬─────► JVM 保证唯一性
🧠 结语:单例模式虽然简单,但它是理解 Java 内存模型、线程安全、类加载机制的绝佳入门实践。
