分类:
单例模式分为懒汉和饿汉两种。饿汉式的写法比较固定也没有风险,懒汉式的写法比较多变且存在各种潜在的风险。
饿汉:
写法为:
A.h
----
class A
{
public:
static A* getA();
private:
static A* _instance;
};
A.cpp
----
#include "A.h"
A* A::_instance = new A();
A* A::getA();
{
return _instance;
}
懒汉:
单锁单check写法:
A.h
----
class A
{
public:
static A* getA()
{
std::lock_guard<std::mutex> lk(_instance_lk);
if(nullptr == _instance){
_instance = new A();
}
return _instance;
}
private:
static std::mutex _instance_lk;
static A* _instance;
};
A.cpp
----
std::mutex A::_instance_lk;
A* A::_instance = nullptr;
这种写法能够保证绝对的线程安全性,但是性能会有很大损失,因为每次 getA() 都一定会加锁。而单例模式恰恰是仅在第一次才会创建实例,也就是说只有第一次才需要加锁,后面的所有加锁都是不必要的。因此便有双验证写法。
单锁双check粗糙写法(有缺陷):
A.h
----
class A
{
public:
static A* getA()
{
if(nullptr == _instance){
std::lock_guard<std::mutex> lk(_instance_lk);
if(nullptr == _instance){
_instance = new A();
}
}
return _instance;
}
private:
static A* _instance;
static std::mutex _instance_lk;
};
A.cpp
----
A* A::_instance = nullptr;
std::mutex A::_instance_lk;
单锁双check写法的初衷是解决单锁单check写法的效率问题,但是很不幸会引入side effect。针对上面的代码做如下假设:
1)线程1分别通过了第一次验证,加锁,第二次验证;
2)线程1开始调用 new 来创建实例,实例创建的过程比较缓慢;
3)线程2抢占到CPU,打断了2)的实例创建操作,这个时候实际上 _instance 已经被赋予某个地址了。因此线程2会直接返回。
new 会做两个动作,一个是分配内存空间,一个是初始化类实例,分配好的内存空间会第一时间赋值给指针 _instance, 因此这个时候 _instance 实际上指向了一个尚未完全初始化完成的内存地址。
4)由于线程2跳过了后续的加锁和第二次判断,返回了一个并没有完全初始化完毕的实例,所以这会导致各种不确定行为。
c++11 写法:
A.h
class A
{
public:
static A& getA();
}
A.cpp
A& A::getA()
{
static A instance;
return instance;
}
这种写法要求c++ 11编译,同时基于不同的编译器可能会有些许差别。不过现在距离c++ 11标准已经有很长时间了,相信基本上所有的编译器都已经完美地修复了这个问题,因此可以放心使用。
注意,这里只能返回引用,因为c++ 11只保证 static 变量的初始化是不可重入的。所以如果希望得到指针的话,还是需要使用单锁双check的方法,下面介绍改善版的单锁双check。
单锁双check安全写法 1:
A.h
----
class A
{
public:
static A* getA()
{
A* tmp = _instance.load(std::memory_order_acquire);
if(nullptr == _instance)
{
std::lock_guard<std::mutex> lk(_instance_lk);
tmp = _instance.load(std::memory_order_relax);
if(nullptr == tmp){
tmp= new A();
_instance.store(tmp, std::memory_order_release);
}
}
return tmp;
}
private:
static std::atomic<A*> _instance;
static std::mutex _instance_lk;
};
A.cpp
----
std::atomic<A*> A::_instance;
std::mutex A::_instance_lk;
这种写法需要使用到 c++ 11 的 atomic 。
单锁双check安全写法 其他: