引言
单例模式是在工程上比较常见的设计模式,顾名思义,单例是指在程序运行周期内有且只有一个实例化的对象。在面试过程中,实现线程安全的单例模式也是个高频考点,本文也是立足于面试,罗列C++的线程安全单例模式的实现方法。
单例的实现方式
根据实例化的时机,主要分为饿汉和懒汉两种。
- 饿汉。第一次获取实例前就已经创建好。
class Singleton { public: static Singleton* GetInstance(); private: static Singleton* instance_; }; Singleton* instance_ = new Singleton(); // 已经创建好 Singleton* Singleton::GetInstance() { return instance_; }
- 懒汉。第一次获取实例时才创建。
class Singleton { public: static Singleton* GetInstance(); private: static Singleton* instance_; }; Singleton* instance_ = nullptr; // 静态变量的初始化,默认为nullptr Singleton* Singleton::GetInstance() { if (instance_ == nullptr) { instance_ = new Singleton(); // instance_为空时才创建 } return instance_; }
线程安全
线程安全问题简单来说就是多线程在对同一共享对象的读写不一致。这里要声明一点,针对单例模式的线程安全是指获取实例这一操作,而不是对获取到的实例对象本身的操作。下面来分析一下上述两种实现方式的线程安全性:
- 在饿汉的方式下,调用
GetInstance()
将直接返回已经创建好的实例,可以认为都是读操作,因此是线程安全的; - 在懒汉的方式下,可以看到
GetInstance()
的注释里说的是“intance_为空时才创建”,而不是“第一次获取实例时才创建”,对于前者一种可能的问题就是两个线程在都进入到了if (instance_ == nullptr)
的判断分支里,都执行了instance_ = new Singleton()
这一步,结果显而易见,Singleton
被实例化两次,这样就导致了线程不安全。
线程安全的懒汉单例模式
既然找到了线程不安全的原因,下面直接给出安全的例子:
- 加互斥锁
class Singleton { public: static Singleton* GetInstance(); private: static Singleton* instance_; static mutex mtx_; }; Singleton* Singleton::instance_ = nullptr; // 静态变量的初始化,默认为nullptr mutex Singleton::mtx_; Singleton* Singleton::GetInstance() { unique_lock<mutex> lock(mtx_); if (instance_ == nullptr) { instance_ = new Singleton(); // instance_为空时才创建 } return instance_; }
- 双重检查锁(DCL)
class Singleton { public: static Singleton* GetInstance(); private: static Singleton* instance_; static mutex mtx_; }; Singleton* Singleton::instance_ = nullptr; // 静态变量的初始化,默认为nullptr mutex Singleton::mtx_; Singleton* Singleton::GetInstance() { if (instance_ == nullptr) { unique_lock<mutex> lock(mtx_); if (instance_ == nullptr) { instance_ = new Singleton(); // instance_为空时才创建 } } return instance_; }
- 内部静态变量
class Singleton { public: static Singleton* GetInstance(); }; Singleton* Singleton::GetInstance() { static Singleton instance; // !指定C++11编译选项 return &instance; }
- unique_lock+call_once
class Singleton { public: static Singleton& GetInstance(); private: static unique_ptr<Singleton> instance_; }; Singleton& Singleton::GetInstance() { static once_flag flag; call_once(flag, [&]() { instance_.reset(new Singleton()); }); return *instance_; }
其他问题
- DCL方法可能会被一些编译器优化,导致乱序问题怎么处理?参考设计模式 - 单例模式 - 《C++那些事(Stories About C Plus Plus)》 - 书栈网 · BookStack
- 创建的单例怎么自动释放呢?参考设计模式 - 单例模式 - 《C++那些事(Stories About C Plus Plus)》 - 书栈网 · BookStack,实现一个内嵌垃圾回收类