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