先看一个最简单的教科书式单例模式:
class CSingleton
{
public:
static CSingleton* getInstance()
{
if (NULL == ps)
{//tag1
ps = new CSingleton;
}
return ps;
}
private:
CSingleton(){}
CSingleton & operator=(const CSingleton &s);
static CSingleton* ps;
};
CSingleton* CSingleton::ps = NULL;
有2个要点:
1.private的构造函数和=操作符,用于防止类外的实例化和被复制;
2.static的类指针和get方法。
在大多数单线程情况下,以上代码大都会运行得很好,除非遇到中断:
1.当程序运行到tag1 处触发了中断;
2.中断处理程序恰调用的也是getInstance函数。
可想而知,这和多线程的情况类似,假设线程A 运行到tag1处,还没来得及new,此时ps仍然是NULL,线程B(或中断处理程序) 同时也运行到此通过if判断,那么将会实例化2个CSingleton对象,显然是不对的。
为了解决上述问题,自然而然,最容易想到也最常用的方法是加锁,因此getInstance改成这样:
static CSingleton* getInstance()
{
lock();//伪代码
if (NULL == ps)
{
ps = new CSingleton;
}
return ps;
}
加了锁以后貌似解决了上述问题,但也同样带来了新的问题:如果程序到处是诸如:
CSingleton::instance()->aaaa();
CSingleton::instance()->bbbb();
CSingleton::instance()->cccc();
这样的调用,除了第一次的lock()有用外,后面的都是在做无用功,lock()的代价说大不大,但在某些情况下还是会提高程序延迟,这对追求完美的程序猿来说是完全无法接受的。
于是乎,咱想出了一个办法:
static CSingleton* getInstance()
{
if (NULL == ps)//这里加了次判断,只有第一次才会为true而调用lock()
{
lock();//伪代码
if (NULL == ps)
{
ps = new CSingleton;
}
}
return ps;
}
很久以后我才知道,这个方法有个很高大上的名字,叫做双重检查锁定模式,简称DCLP(Double Checked Locking Pattern)。
DCLP很好地解决了多次调用不必要的lock()。
然而,你们以为这样就完了?too young。。
DCLP在多线程下仍然存在2个根本问题:
1.程序的指令执行顺序不确定;
2.编译器优化问题。
先说2,在某些编译器下,以上的两个if判断只会执行一个,甚至一个都不执行,原因是编译器认为至少有一个if判断是多余的,它自动帮助我们优化了代码。
再说1,ps = new CSingleton; 这条语句会被拆分为这样的三个步骤执行:
1.为要new的对象开辟一块内存;
2.构造该对象,填入这块内存;
3.将ps指针指向这块内存。
以上三个步骤,2和3的顺序是不确定的,可能先2后3,也可能先3后2。。。










