"懒汉"模式虽然有优点, 但是每次调用 GetInstance()静态方法时, 必须判断NULL == m_instance, 使程序相对开销增大。多线程中会导致多个实例的产生, 从而导致运行代码不正确以及内存的泄露。
对于多线程的问题,我们可以看下面这个例子:
#include <iostream>
#include <windows.h>
using namespace std;
//懒汉式
class Singleton
{
private:
Singleton(){
Sleep(10);
}
public:
static Singleton* getInstance(){
if(sin == NULL)//懒汉式: 1 每次获取实例都要判断 2 多线程会有问题
{
sin = new Singleton;
}
return sin;
}
static void deleteInstance(){
if(sin != NULL)
{
delete sin;
sin = NULL;
}
}
private:
static Singleton * sin;//未初始化
};
Singleton * Singleton::sin = NULL;
假设有多线程,第一个线程进入实例化,然后会运行Sleep函数,第二个线程肯定不是等上一个线程Sleep运行完才执行,然而上一个线程没有实例化完成,所以同样会进行实例化,然后运行Sleep,这样的话,等到Sleep运行完,会有多个实例产生。这样单例模式就失去了意义,浪费了开销。
懒汉式遇上多线程,将可能不再是单例。
解决方法:Double-Checked Locking(两次检查)
代码如下:
//临界区
static CCriticalSection cs;
static Singleton *Instantialize()
{
if(pInstance == NULL) //double check
{
cs.Lock(); //只有当 pInstance 等于 null 时, 才开始使用加锁机制 二次检查
if(pInstance == NULL)
{
pInstance = new Singleton();
}
cs.Unlock();
} return pInstance;
}
static Singleton *pInstance;
解读:这里有一个临界区的概念,若想详细查看,可看操作系统相关。
- 若第一个线程进入,第一次判断为空,继续进入,进行加锁,判断为空,继续进入创建实例。
- 在第一个线程创建实例过程中,第二个线程也进入到加锁这行代码,犹豫加锁后还没解锁,线程2进入进入阻塞状态等待解锁,同样后面的线程3等也会进入此状态。
- 之后,若线程1执行完实例化,解锁,线程2开始执行,但是已经实例化,所以不重新实例化,解锁跳出,之后的线程也是如此。新进入的线程第一次判空便会直接跳出。