文章目录
未经授权,禁止转载!创作不易,尊重原创!~~
1. 背景
在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性,以及良好的效率。
如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?
这应该是类设计这的责任,而不是使用者的责任。
2. 定义
保证一个类仅有一个实例,并提供一个该实例的全局访问点。 《设计模式》 GoF
3. 代码实现
(1) Singleton类的特点
- 构造函数被被声明成私有函数,防止用户直接调用构造函数实例化类。在用户没有自己写构造函数的情况下,编译器会默认为类生成两个公有的构造函数:无参数构造函数和拷贝构造函数。因此除了用户自己写的构造函数会被声明成私有的,这两个函数也要确保是私有的。
- 类的实例化是通过调用静态成员函数getInstance()来实现的,获取类的对象也是通过这个对象来实现的。
(2) Singleton类的代码实现
class Singleton {
private:
Singleton();
Singletion(const Singleton& other);
public:
static Singleton* getInstance();
static Singleton* m_instance;
};
(3) getInstance()的代码实现
方案1:单线程版本,多线程不安全
Singleton* Singleton::get_instance() {
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
方案2: 锁后判断,线程安全,缺陷是代价太高
代价太高的原因
在多线程下,写操作是需要线程保护的,读操作是不需要线程保护的。对应到这里就是只有在第一次实例化Singletonl类的时候(写操作)需要加锁保护,后续调用getInstance函数获取对象指针的时候(读操作)不需要加锁保护。而方案2的代码实现,无论读写操作都需要加锁。这显然限制了读操作的效率, 不适合高并发的项目
Singleton* Singleton::get_instance() {
Lock lock;
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
方案3: 锁前判断,达不到单例的目的
**达不到单例的目的的原因
线程A在执行完if判断后时间片耗完,此时线程B执行if判断。此时线程A和B判断的结果都是true, 都会执行判断体里面的代码,此时Singleton对象会被多次实例化。
Singleton Singleton::get_instance() {
if (m_instance == nullptr) {
Lock lock;
m_instance = new Singleton();
}
return m_instance;
}
方案4:double check, 锁前锁后都加判断,由于内存读写reorder导致不安全
由于内存读写reorder导致不安全的解释
m_instance = new Singleton();
这行代码编译器执行的时候的正常情况下会拆分成三个步骤:
step1:分配一片内存空间用于实例化Singleton对象,
step2:通过构造函数实例化对象,即对这片内存空间进行读写,
step3:完成实例化后,将这片内存的首地址赋给指针m_instace。
由于编译器的reorder优化,可能这行代码上述步骤完成,他可能将上述的step2和step3反过来执行。
应用到方案4的实现上,如果线程A使用了reorder优化,线程A执行完step1和step3,时间片耗完。此时线程B执行getInstance函数,线程B在第一个判断时,发现m_instance不为nullptr, 继续往下执行,由于线程A并没有对这片内存进行初始化,因此线程B从线程中获取的内容都是随机的。
由于这是编译器优化上的bug,理论上来说在代码上是无法修复的。除非用户显示指定编译器不对上述代码进行优化,这就是方案5的实现
方案5:限制reorder操作的double check
c# , java会通过volatile关键字来告诉编译器不要进行reorder,后来微软的c++平台VC++也加入了这个关键字。但跨平台的实现还是通过以下代码。
// c++1版本之后的跨平台实现(volatile)
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;
Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::mempry_order_acquire); // 获取内存fence
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); // 释放内存fence
m_instance.store(tmp.std::memory_order_relaxed);
}
}
return tmp;
}