设计模式十:单件模式 (Singleton Pattern)

单件模式是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点。

单例模式有两种主要的初始化方式:饱汉模式 (Lazy Initialization)和饿汉模式(Eager Initialization)。它们在实例创建的时机上有显著区别。

1. 饱汉模式 (Lazy Initialization)

饱汉模式也称为"懒加载"模式,只有在第一次请求实例时才创建单例对象。

特点:

  • 延迟初始化 :实例在第一次调用getInstance()时才被创建

  • 节省资源:如果从未使用单例,则不会创建实例

  • 需要处理线程安全问题

基础实现(非线程安全):

复制代码
class Singleton {
private:
    Singleton() {}
    static Singleton* instance;
    
public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
    
    // 删除拷贝构造函数和赋值运算符
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

Singleton* Singleton::instance = nullptr;

线程安全版本:

复制代码
#include <mutex>

class Singleton {
private:
    Singleton() {}
    static Singleton* instance;
    static std::mutex mtx;
    
public:
    static Singleton* getInstance() 
    {
        std::lock_guard<std::mutex> lock(mtx);
        if (instance == nullptr)
        {  
            instance = new Singleton();
        }
        return instance;
    }
    
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

此方法线程安全,但是锁的代价太高,每次访问都需要加锁,开销较大,需要优化 。

双检查锁(但由于内存读写reorder不安全) :

复制代码
#include <mutex>

class Singleton {
private:
    Singleton() {}
    static Singleton* instance;
    static std::mutex mtx;
    
public:
    static Singleton* getInstance() {
        if (instance == nullptr) {  // 第一次检查
            std::lock_guard<std::mutex> lock(mtx);
            if (instance == nullptr) {  // 第二次检查
                instance = new Singleton();
            }
        }
        return instance;
    }
    
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

正常的逻辑是(1、先分配内存,2、最后再构造 ,3、把内存分配给instance ),但是由于内存读写reorder,可能会导致其他线程访问在第一次检查时读取了reorder(可能最开始的线程1、先分配内存,2、把内存分配给instance ,3、最后再构造)的非正确值(第2步的内存),出现错误。

C++11版本之后的跨平台实现(volatile) (线程安全)

复制代码
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    // 1. 首先以宽松内存序读取当前实例
    Singleton* tmp = m_instance.load(std::memory_order_relaxed);
    
    // 2. 获取内存栅栏,确保后续读取操作能看到之前的所有写入
    std::atomic_thread_fence(std::memory_order_acquire);
    
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load(std::memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new Singleton;
            
            // 3. 释放内存栅栏,确保新对象的构造对所有处理器可见
            std::atomic_thread_fence(std::memory_order_release);
            
            // 4. 以宽松内存序存储新实例
            m_instance.store(tmp, std::memory_order_relaxed);
        }
    }
    return tmp;
}

2. 饿汉模式 (Eager Initialization)

饿汉模式在程序启动时(静态初始化阶段)就创建单例实例。

特点:

  • 提前初始化:实例在程序启动时就被创建

  • 线程安全:因为实例在main()函数执行前就已创建

  • 可能浪费资源:即使从未使用单例,实例也会被创建

实现:

复制代码
class Singleton {
private:
    Singleton() {}
    static Singleton* instance;
    
public:
    static Singleton* getInstance() {
        return instance;
    }
    
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

// 在程序启动时就初始化实例
Singleton* Singleton::instance = new Singleton();

使用静态变量的饿汉模式:

复制代码
class Singleton {
private:
    Singleton() {}
    
public:
    static Singleton& getInstance() {
        static Singleton instance;  // 静态变量在首次使用时初始化(C++11保证线程安全)
        return instance;
    }
    
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

对比总结

特性 饱汉模式 饿汉模式
初始化时机 第一次调用getInstance()时 程序启动时
线程安全 需要额外处理 天生线程安全
资源占用 按需分配,节省资源 始终占用资源
实现复杂度 较复杂(需处理线程安全) 较简单
适用场景 初始化耗时或资源占用大的对象 初始化快且一定会使用的对象
相关推荐
AI人工智能+电脑小能手19 分钟前
大白话说Java设计模式-28-模板方法模式(业务实战篇)
java·设计模式·模板方法模式·spring源码·订单系统·代码复用
leeyi4 小时前
两个 Agent 怎么协作:Host-Worker 模式(第91篇-E77)
设计模式·agent·ai编程
AI人工智能+电脑小能手21 小时前
大白话说Java设计模式-26-策略模式(业务实战篇)
java·spring·设计模式·策略模式·支付系统·算法切换
星火10241 天前
【Groovy翻译-进阶篇】Groovy 中的设计模式
后端·设计模式·groovy
AI人工智能+电脑小能手1 天前
大白话说Java设计模式-27-策略模式(源码剖析篇)
java·spring·设计模式·策略模式·源码分析·事务管理·comparator
SL_staff1 天前
风控规则如何从硬编码解耦?我们在 JVS-Rules 中的实践与思考
java·spring boot·设计模式
小此方1 天前
Linux加餐(一):藏在Linux中的设计模式(一)策略模式与日志
linux·设计模式·策略模式
To_OC2 天前
从一行入口代码啃透 NestJS:工厂模式、装饰器和依赖注入是怎么串起来的
后端·设计模式·nestjs
东方小月2 天前
从零开发一个 Coding Agent(十二):实现版本化 JSONL 与真实 CLI 入口
前端·设计模式·前端框架
程序员贺加贝2 天前
轻制造SaaS的生产闭环建模-BOM工单领料报工质检与入库
java·设计模式·架构