欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 金融 > 10.单例模式 (Singleton Pattern)

10.单例模式 (Singleton Pattern)

2025/2/10 4:24:31 来源:https://blog.csdn.net/Tiantangbujimo7/article/details/145457986  浏览:    关键词: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模式的初衷违背。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com