一.定义
保证一个类仅有一个实例,并提供一个该实例的全局访问点。
二.类图
三.案例
分享几个常见的错误案例并且分析错误原因:
版本1.
class Singleton {
public:
static Singleton *Instance() {
if (instance == nullptr) {
instance = new Singleton;
}
return instance;
}
~Singleton() = default;
private:
Singleton() = default;
Singleton(const Singleton &other) = default;
Singleton& operator=(const Singleton &other) = default;
static Singleton *instance;
};
Singleton *Singleton::instance = nullptr;
版本1有两个问题:
1. 单线程存在内存泄漏问题(instance指针作为static对象,在程序结束时会释放。但是instance指针所指对象存在于堆上)
2. 多线程存在线程安全问题
版本2.
class Singleton {
public:
static Singleton *Instance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(_mutex);
if (instance == nullptr) {
instance = new Singleton;
// 结束时自动调用
atexit(Destroy);
}
}
return instance;
}
~Singleton() = default;
private:
Singleton() = default;
Singleton(const Singleton &other) = default;
Singleton& operator=(const Singleton &other) = default;
// 销毁
static void Destroy() {
delete instance;
instance = nullptr;
}
static Singleton *instance;
// 互斥锁
static std::mutex _mutex;
};
Singleton *Singleton::instance = nullptr;
版本2解决了单线程情况下的释放问题,在atexit()中注册销毁函数,在程序结束时自动调用销毁函数对堆区进行释放。
版本2还通过使用互斥锁试图解决线程安全问题,但是实际上版本2依然存在线程安全问题。
在计算机底层中,new操作会被拆分为3个原子操作,分别是:(1)分配内存 (2)调用构造函数 (3)给指针(instance)赋值
但是由于可能出现指令reorder,导致原本1,2,3顺序变为1,3,2顺序,此时单例模式发生问题。在线程1中,指令执行到3之后进行线程切换,此时线程2执行第一个if语句发现instance并不为空,于是返回。但此时instance所指内存有可能为随机值,最终导致发生coredump。
正确的方法:
版本1.
class Singleton {
public:
static Singleton *Instance() {
static Singleton instance;
return &instance;
}
~Singleton() = default;
private:
Singleton() = default;
Singleton(const Singleton &other) = default;
Singleton& operator=(const Singleton &other) = default;
};
C++11提供了magic static特性,此特性可以保证变量在初始化时,并发进入声明语句,并发线程将会阻塞等待初始化结束,这样保证了线程安全。同时因为instance对象为static类型,所以会自动释放。
版本2.
class Singleton {
public:
static Singleton *Instance() {
if (instance == nullptr) {
pthread_once_t once = PTHREAD_ONCE_INIT;
pthread_once(&once, init);
}
return instance;
}
~Singleton() = default;
private:
Singleton() = default;
Singleton(const Singleton &other) = default;
Singleton& operator=(const Singleton &other) = default;
static Singleton *instance;
static void init() {
instance = new Singleton;
atexit(destroy);
}
static void destroy() {
delete instance;
instance = nullptr;
}
};
Singleton *Singleton::instance = nullptr;
若没有magic static特性可以使用系统调用pthread_once()解决线程安全问题。