单例模式的意图是:“保证一个类仅有一个实例,并提供一个访问它的全局访问点。--《DP》”
从单例模式的目的来看,类的构造函数不能是public属性的,不然使用者都可以调用,也就不止一个实例对象。提供一个全局访问点意味着需要一个静态的访问函数。其实这里的实现思想也非常朴素,既然只能有一个实例,那么就在构造前判断一下是否存在对象,存在就直接返回,不存在就构造。是不是非常类似于微机原理中标志位的感觉。那么上代码:
singleton.h
#pragma once
#ifndef SINGLETON_H
#define SINGLETON_H
class Singleton {
public:
// 暴露给外部的全局访问点
static Singleton* GetInstance();
protected:
private:
static Singleton* instance;
Singleton();
Singleton(Singleton& s);
Singleton operator = (const Singleton);
};
#endif // !SINGLETON_H
singleton.cpp
#include "singleton.h"
Singleton* Singleton::instance = nullptr;
Singleton* Singleton::GetInstance()
{
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
Singleton::Singleton()
{
}
验证如下:
#include "singleton.h"
#include <iostream>
using namespace std;
int main()
{
Singleton* s1 = Singleton::GetInstance();
Singleton* s2 = Singleton::GetInstance();
cout << "s1: " << s1 << endl;
cout << "s2: " << s2 << endl;
return 0;
}
运行结果如下:
image.png
可以看出,虽然想构造两个对象,但是指向的都是同一个实例对象。
如上代码在单线程场景下,功能是没有问题的,反之,多线程则会出现问题。它不是线程安全的实现。
假设线程1运行到Singleton* Singleton::GetInstance()函数if分支中,假设此时instance=nullptr,并且判断完毕,此时CPU时间片切到线程2,恰好也是这个函数,由于此时instance的值是nullptr,那么会new一个实例。再次切换到线程1,执行new实例操作,结果就创建了两个不同的实例。
现在来修改下GetInstance函数:
Singleton* Singleton::GetInstance()
{
Lock lock;
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
这里的实现可以避免上述的问题。但是这个实现也不是很合理,上述问题出现的前提是instance=nullptr,没必要进来就加锁,而且高并发的情况下影响性能。检查后再加锁的做法被称为“double-check locking”(双重锁定或双检查锁)。
Singleton* Singleton::GetInstance()
{
if (instance == nullptr) {
Lock lock;
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
}
双重锁定的实现在通常情况下似乎没有问题,但是假如编译器做了一些“优化”:reorder,上述代码依然有漏洞。
让我们看下instance = new Singleton()语句的具体实现:
1、申请内存;
2、调用Singleton构造器;
3、把内存地址传给instance;
如果编译器进行优化后,变成如下顺序:
1、申请内存;
2、把内存地址传给instance;
3、调用Singleton构造器;
假设线程1执行完申请内存,并且把内存地址传给instance(即instance != nullptr)后,挂起,线程2执行到GetInstance函数,此时(instance == nullptr)的结果是个false,直接返回地址。使用这个指针就会出现问题,因为实际上它还是个原生指针,还没有被类构造!
关于如何修改,李建忠给出了一种想法,我觉得太繁琐了。地址在https://www.bilibili.com/video/av24176315?t=1895&p=12 P12部分,时间26:00。
后续继续更新。
网友评论