懒汉模式
- 在多线程情况下,懒汉模式是线程是不安全的,如果多个线程同时去访问单例对象,而这个单例对象尚未初始化,那么在判断 instance == nullptr 的时候,可能会出现 instance 已经在创建了,但是判断结果还是为true,就会创建出多个单例对象的实例;
解决方案:使用双检查锁;使用静态局部变量也可以解决;
为什么要两次判断 instance == nullptr 之后,才初始化单例对象?
假设此时单例对象还没有创建,有两个线程同时进入单例对象的访问函数,此时两个线程都通过的第一次为空的判断,在两次判断之间有一把互斥锁,只有一个线程可以获取,该线程获取后,第二次判断为空,开始初始化单例对象,初始化结束后释放锁;此时另一个线程进入第二次为空判断,发现单例对象已经初始化;通过双检查锁的机制,可以避免线程安全问题;
为什么互斥锁在两次检查之间获取,而不是在第一次检查开始前直接获取?
如果放在第一次检查前,那么每次获取单例对象,都要先获取互斥锁,会带来极大的资源浪费;
- 单例模式的默认构造函数需要设置为private,且拷贝构造函数和 "=" 赋值运算符都要禁用;
单例模式的默认构造函数设置为private是为了防止构造函数被外部调用,如果外部可以调用单例类的构造函数,那么这就不会仅存在一个单例;
禁用拷贝构造函数和 "=" 赋值运算符也是同理,反之单例类对象被多次创建;
cpp
// 线程安全的懒汉模式
#include <mutex>
class SingleClass {
public:
SingleClass(const SingleClass&) = delete;
SingleClass& operator=(const SingleClass&) = delete;
static SingleClass *getInstance() {
if (instance == nullptr) {
m_mutex.lock();
if (instance == nullptr) {
instance = new SingleClass();
}
m_mutex.unlock();
}
return instance;
}
private:
static SingleClass *instance;
static std::mutex m_mutex;
SingleClass() = default;
};
// 静态变量需要在类内声明,在类外初始化
SingleClass *SingleClass::instance = nullptr;
std::mutex SingleClass::m_mutex;
饿汉模式
- 与懒汉模式的区别就在于,在程序启动时,或在类加载时编译期,就将单例类实例化,所以不需要双检查锁,也就不存在线程安全问题;在获取单例实例的时候,直接return instance 即可。