设计模式实战篇(一):彻底搞懂 Singleton 单例模式

💬 关键词:创建型设计模式、线程安全、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 内存模型、线程安全、类加载机制的绝佳入门实践。

相关推荐
Mr.wangh2 小时前
单例模式&阻塞队列详解
java·开发语言·单例模式·多线程·阻塞队列
工业甲酰苯胺2 小时前
TypeScript 中的单例模式
javascript·单例模式·typescript
喝拿铁写前端16 小时前
从面条代码到抽象能力:一个小表单场景里的前端成长四阶段
前端·设计模式·架构
依米_16 小时前
一文带你剖析 Promise.then all 实现原理,状态机、发布订阅模式完美实现异步编程
javascript·设计模式
jzhwolp17 小时前
从基本链表到侵入式链表,体会内核设计思路
c语言·后端·设计模式
李宥小哥1 天前
结构型设计模式1
设计模式
lapiii3581 天前
[智能体设计模式] 第五章 :函数调用
microsoft·设计模式
lapiii3581 天前
[智能体设计模式] 第 1 章:提示链(Prompt Chaining)
设计模式·prompt
昨天的猫1 天前
《拒绝重复代码!模板模式教你优雅复用算法骨架》
后端·设计模式