引言
单例设计模式(Singleton Design Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取该实例。这种模式常用于需要控制资源访问、配置管理或共享资源等场景。
本文主要介绍单例设计模式的两种实现方式及其特点。
懒汉模式
-
懒汉模式创建单例对象,是在需要用到该对象的时候才会去创建单例对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20class Singleton { public: static Singleton* getInstance(){ //需要用到的时候,调用getInstance创建单例对象 if (nullptr == m_pSingleton) { m_pSingleton = new Singleton(); } return m_pSingleton; } private: static Singleton* m_pSingleton; Singleton() {std::cout << "create instance" << std::endl;} ~Singleton() {std::cout << "destroy instance" << std::endl;} // 删除拷贝构造函数和赋值运算符,确保单例唯一性 Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; }; Singleton* Singleton::m_pSingleton = nullptr;
懒汉模式在多线程环境下,存在线程不安全性,当多个线程都同时调用getInstance时,如果此时并没有创建单例对象出来,那么多个线程可能同时走到nullptr == m_pSingleton当中去,这时候多个线程同时new单例对象,导致了不安全行为。
- 解决线程安全可以通过加锁来解决
|
|
- 下面还有一种双重检查的方式,这种方式看似更加麻烦,但是也有设计巧妙的地方,读者可以好好思考一下为什么。
|
|
-
上面这种模式为什么要检查两次呢,这不是更加麻烦吗?其实里面也有巧妙的设计。
- 如果按照只检查一次的代码,那么无论什么时候去调用getInstance()都会发生加锁动作。
- 但是双重检查版本只有在单例对象没有创建的时候才会发生加锁,当对象已经被创建之后,第一次检查nullptr == instance就会跳过加锁过程,直接返回对象。这样会大大减少加锁的开销。
-
2025-4-13更新
-
上面那种模式还会涉及一些内存顺序的问题,instance = new Singleton(); 会被分为三个步骤,分配内存,构造,将地址传给instance。但是有时候指令重排会将三个步骤重排为分配内存,将地址传给instance,构造,这时候当两个线程按下面流程进行getInstance()就会发生问题。
-
线程A 线程B if (instance == nullptr) std::lock_guard<std::mutex> lock(mutex); // 加锁 if (instance == nullptr) { // 第二次检查 分配内存 将地址给instance (instance不再是nullptr了) if (instance == nullptr) return instance 开始使用instance(???instance好像还没有构造) 构造instance
-
-
解决办法就是使用c++当中的原子操作(atomic的操作可以指定一些内存顺序,内存顺序和atomic的内容以后再更新,这里主要强调单例模式的实现)
|
|
饿汉模式
-
在定义对象的时候,就会创建单例对象出来,这个对象会存在程序的整个作用域。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21class Singleton { public: static Singleton* getInstance(){ if (nullptr == m_pSingleton) { m_pSingleton = new Singleton(); } return m_pSingleton; } private: int data = 0; static Singleton* m_pSingleton; Singleton() {std::cout << "create instance" << std::endl;} ~Singleton() {std::cout << "destroy instance" << std::endl;} // 删除拷贝构造函数和赋值运算符,确保单例唯一性 Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; }; Singleton* Singleton::m_pSingleton = getInstance(); -
单例对象在程序启动后就进行了创建,多线程模式下,并不会出现线程安全问题,每个线程都是直接使用已经创建的单例对象。