一、懒汉模式
懒汉模式存在线程安全问题
1、双重检测锁(一坨)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| #ifndef DOUBLE_CHECKED_SINGLETON_H #define DOUBLE_CHECKED_SINGLETON_H
#include <iostream> #include <mutex> #include <atomic> #include <memory>
class DoubleCheckedSingleton { public: DoubleCheckedSingleton(const DoubleCheckedSingleton&) = delete; DoubleCheckedSingleton& operator=(const DoubleCheckedSingleton&) = delete;
static DoubleCheckedSingleton* getInstance() { DoubleCheckedSingleton* p = instance.load(std::memory_order_acquire); if (p == nullptr) { std::lock_guard<std::mutex> lock(mutex); p = instance.load(std::memory_order_relaxed); if (p == nullptr) { p = new DoubleCheckedSingleton(); instance.store(p, std::memory_order_release); } } return p; }
static void destroyInstance() { std::lock_guard<std::mutex> lock(mutex); if (instance != nullptr) { delete instance.load(); instance = nullptr; } }
~DoubleCheckedSingleton() { std::cout << "双重检测锁单例被销毁" << std::endl; }
private: DoubleCheckedSingleton() = default;
static std::atomic<DoubleCheckedSingleton*> instance; static std::mutex mutex; };
std::atomic<DoubleCheckedSingleton*> DoubleCheckedSingleton::instance{nullptr}; std::mutex DoubleCheckedSingleton::mutex;
#endif
|
双重检测锁在C++中存在内存顺序问题。例如,instance = new DoubleCheckedSingleton()这行代码在编译器优化下可能会重排序为:
- 分配内存
- 将指针赋值给instance变量
- 构造对象
如果执行顺序变为1→2→3,在进行到2时实例已经非空,但实例还未构建,如果此时有第三个线程想要获取实例,就会返回一个空的实例!
2、静态局部变量(最推荐的方式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #ifndef STATIC_LOCAL_SINGLETON_H #define STATIC_LOCAL_SINGLETON_H
#include <iostream>
class StaticLocalSingleton { public: StaticLocalSingleton(const StaticLocalSingleton&) = delete; StaticLocalSingleton& operator=(const StaticLocalSingleton&) = delete; static StaticLocalSingleton& getInstance() { static StaticLocalSingleton instance; return instance; } ~StaticLocalSingleton() { std::cout << "静态局部变量单例被销毁" << std::endl; }
private: StaticLocalSingleton() = default; };
#endif
|
二、饿汉模式
在程序加载时就创建,天然线程安全,无需加锁
1、引用方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #ifndef EAGER_SINGLETON_H #define EAGER_SINGLETON_H
#include <iostream>
class EagerSingleton { public: EagerSingleton(const EagerSingleton&) = delete; EagerSingleton& operator=(const EagerSingleton&) = delete; static EagerSingleton& getInstance() { return instance; } ~EagerSingleton() { std::cout << "饿汉单例被销毁" << std::endl; }
private: EagerSingleton() = default; static EagerSingleton instance; };
EagerSingleton EagerSingleton::instance;
#endif
|
2、指针方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #ifndef EAGER_SINGLETON_PTR_H #define EAGER_SINGLETON_PTR_H
#include <iostream>
class EagerSingletonPtr { public: EagerSingletonPtr(const EagerSingletonPtr&) = delete; EagerSingletonPtr& operator=(const EagerSingletonPtr&) = delete; static EagerSingletonPtr* getInstance() { return instance; } static void destroyInstance() { if (instance != nullptr) { delete instance; instance = nullptr; } } ~EagerSingletonPtr() { std::cout << "饿汉单例被销毁" << std::endl; }
private: EagerSingletonPtr() = default; static EagerSingletonPtr* instance; };
EagerSingletonPtr* EagerSingletonPtr::instance = new EagerSingletonPtr();
#endif
|
三、补充
C++单例类初始化顺序会导致一些问题,详细可以查看这篇文章