当前位置: 首页 > article >正文

10.单例模式 (Singleton Pattern)

单例模式的定义

单例模式(Singleton Pattern) 是一种创建型设计模式,确保一个类在整个程序生命周期中只能有一个实例,并提供一个全局访问点。

特点:

  • 唯一性:保证系统中某个类只能有一个实例。
  • 全局访问点:提供全局的静态方法来访问这个唯一实例。
  • 懒加载(Lazy Initialization):实例只会在第一次访问时创建,节省资源。

适用场景

单例模式常用于以下场景:

  • 管理共享资源(如数据库连接池、线程池、日志管理器)。
  • 全局状态管理(如游戏引擎中的配置管理器)。
  • 设备驱动(如打印机管理类,确保同时只有一个任务访问)。
  • 缓存(如 DNS 解析缓存)。
  • 配置管理(如应用程序的配置文件管理)。

在软件系统中,经常有这样一些特殊类,必须保证它们在系统中只存在一个实例,才能确保他们的逻辑正确性,以及良好的效率。

如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?

class Singleton{
private:
    Singleton();
    Singleton(const Singleton& other);
public:
    static Singleton* getInstance();
    static Singleton* m_instance;
};

Singleton* Singleton::m_instance=nullptr;

//线程非安全版本
Singleton* Singleton::getInstance() {
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

上述这种实现在多线程的情况下并不安全。解决这种情况可以加锁,但是锁的代价太大,因为每次访问都会被锁。

//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {
    Lock lock;
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

加双检查锁(锁前锁后双检查)就可以避免上述这个问题:

//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
    
    if(m_instance==nullptr){
        Lock lock;
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
    }
    return m_instance;
}

上述这种实现仍然会存在一些问题。线程是在指令层次抢时间片,也就是 m_instance = new Singleton(); 这一行代码可能会先分配内存,然后把内存地址给 m_instance,然后再执行构造器。这样的话就会存在问题,当线程1还没有执行构造函数,但是已经有了地址 m_instance 之后,另外一个线程进来发现 m_instance 不是nullptr就直接返回了这个地址。但是这个地址是不能用的,因为还没有调用构造器。

经典的 Singleton 实现(C++11 之前)

在 C++11 之前,实现单例模式时,需要考虑线程安全和双重检查锁定(DCLP, Double-Checked Locking Pattern):

class Singleton {
private:
    static Singleton* instance;  // 静态指针,指向唯一实例
    static std::mutex m_mutex;   // 互斥锁,确保线程安全
    Singleton() {}               // 构造函数私有,防止外部创建对象
    ~Singleton() {}
public:
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton* getInstance() {
        if (instance == nullptr) {  // 第一次检查(避免不必要加锁)
            std::lock_guard<std::mutex> lock(m_mutex);
            if (instance == nullptr) {  // 第二次检查,确保不会创建多个实例
                instance = new Singleton();
            }
        }
        return instance;
    }
};

// 初始化静态成员
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::m_mutex;

缺点:

  • 需要手动管理 instance 的内存释放,可能导致内存泄漏。
  • 在多线程环境下,需要加锁,可能会影响性能。

C++11 之后的跨平台实现

C++11 提供了 std::atomic 和 std::mutex,使得单例模式可以更加高效和安全。

#include <atomic>
#include <mutex>

class Singleton {
private:
    static std::atomic<Singleton*> m_instance; // 使用 atomic 保证实例安全
    static std::mutex m_mutex;                 // 互斥锁,用于加锁
    Singleton() {}  // 构造函数私有
    ~Singleton() {}

public:
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton* getInstance() {
        Singleton* tmp = m_instance.load(std::memory_order_relaxed);  // 先读取原子变量
        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;
                std::atomic_thread_fence(std::memory_order_release);  // 释放屏障,确保构造完成
                m_instance.store(tmp, std::memory_order_relaxed);
            }
        }
        return tmp;
    }
};

// 初始化静态成员
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

实现步骤
1. 使用 std::atomic 存储 m_instance,确保在多线程环境下安全访问单例实例。

2. 使用 std::mutex 进行加锁,保证线程安全。

3. 双重检查锁定(DCLP):
3.1 第一次检查 if (tmp == nullptr),避免不必要的加锁。
3.2 第二次检查 if (tmp == nullptr),防止多个线程同时进入加锁区域导致创建多个实例。

4.使用 std::atomic_thread_fence:
4.1 memory_order_acquire 确保 tmp 读取到最新的数据。
4.2 memory_order_release 确保 m_instance 赋值完成后,其他线程能看到完整对象
  1. 更加现代的实现方式(C++11 及之后)
    C++11 提供了更简单、安全的 std::call_once,可以替代 std::mutex 和 std::atomic。
#include <mutex>

class Singleton {
private:
    Singleton() {} // 私有构造函数
    ~Singleton() {}
    static std::once_flag initFlag;  // 用于确保初始化只执行一次
    static Singleton* instance;

public:
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton* getInstance() {
        std::call_once(initFlag, []() { instance = new Singleton(); }); // 只执行一次
        return instance;
    }
};

// 初始化静态成员
Singleton* Singleton::instance = nullptr;
std::once_flag Singleton::initFlag;

优势:

  • std::call_once 比锁更高效,因为它只在 getInstance() 第一次调用时执行初始化。
  • 避免了 std::mutex 的加锁开销,减少性能损耗。
  1. C++11 线程安全的懒汉式(推荐)
    如果 C++11 及以上,可以直接使用局部静态变量(Meyers’ Singleton)。
class Singleton {
private:
    Singleton() {}
    ~Singleton() {}
public:
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton& getInstance() {
        static Singleton instance; // 线程安全的懒汉式单例
        return instance;
    }
};

优势:

  • 线程安全:C++11 标准保证了局部静态变量的初始化是线程安全的。
  • 简单:没有 std::mutex 或 std::atomic,代码更易读。
  • 生命周期管理:instance 会在程序结束时自动销毁。

典型调用方式

int main() {
    Singleton* instance1 = Singleton::getInstance();
    Singleton* instance2 = Singleton::getInstance();

    if (instance1 == instance2) {
        std::cout << "两个实例是相同的,单例模式成功!" << std::endl;
    }
    return 0;
}

总结

方式线程安全实现复杂度适用场景
传统懒汉式× 否简单仅限单线程
DCLP(双重检查锁定)√ 是复杂C++11 之前的多线程
std::atomic√ 是较复杂需要高性能
std::call_once√ 是简单现代 C++ 推荐
局部静态变量√ 是最简单C++11 推荐

要点总结

Singleton模式中的实力构造器可以设置为protected以允许子类派生。

Singleton模式一般不要支持拷贝构造函数和Clone接口,因为这有可能导致多个对象实例,与Singleton模式的初衷违背。


http://www.kler.cn/a/533570.html

相关文章:

  • 100.6 AI量化面试题:如何评估AI量化模型的过拟合风险?
  • 企业高效管理策略中的关键一环:WorkWin 监控上网时间的软件的效能剖析
  • DIY Shell:探秘进程构建与命令解析的核心原理
  • Vue Dom截图插件,截图转Base64 html2canvas
  • Verilog基础(三):过程
  • Nacos 的介绍和使用
  • 防火墙策略
  • react的antd表格数据回显在form表单中
  • 2024 TCSVT: LS2T2NN
  • 深入解析 Chrome 浏览器的多进程架构:标签页是进程还是线程?(中英双语)
  • 20250205——Windows系统基于ollama的DeepSeek-R1本地安装
  • 备战蓝桥杯-并查集
  • 【力扣】54.螺旋矩阵
  • PyQt6/PySide6 的 QMainWindow 类
  • 数据传输-工作习惯问题
  • CNN的各种知识点(五):平均精度均值(mean Average Precision, mAP)
  • GaussDB安全配置建议
  • 本地安装部署deepseek
  • 使用 Swift 完成FFmpeg音频录制、播放和视频格式转换应用
  • RabbitMQ 从入门到精通:从工作模式到集群部署实战(一)
  • 【OpenCV实战】基于 OpenCV 的多尺度与模板匹配目标跟踪设计与实现
  • 简易C语言矩阵运算库
  • 【C语言】指针详细解读3
  • 激光工控机在自动化领域中有哪些作用?
  • vim modeline
  • CTP查询资金费率和手续费没响应