单实例模式在对象创建和销毁时存在多线程安全问题,所以通常在这两个方法中加锁。为了有较高的效率,通常采用“双重检查加锁”机制。
#pragma once
template<class T, typename Synch = CLock>
class TSingleton
{
public:
TSingleton(){};
virtual ~TSingleton(){};
static T* instance()
{
if(m_pInstance == NULL)
{
m_lock.Lock();
if (m_pInstance == NULL)
{
m_pInstance = new T();
}
m_lock.Unlock();
}
return m_pInstance;
}
static void release()
{
if(m_pInstance != NULL)
{
m_lock.Lock();
if (m_pInstance != NULL)
{
delete m_pInstance;
m_pInstance = NULL;
}
m_lock.Unlock();
}
}
private:
TSingleton(const TSingleton&);
TSingleton& operator=(const TSingleton&);
private:
volatile static T* m_pInstance;
static Synch m_lock;
};
template<class T, typename Synch> T* TSingleton<T, Synch>::m_pInstance = NULL;
template<class T, typename Synch> Synch TSingleton<T, Synch>::m_lock;
使用:
class Test
{
friend class TSingleton<Test>;
......
};
如TSingleton<Test>即为一个单例模式的类实例。
可以
typedef TSingleton<Test> SingletonTest;
或
using SingletonTest = TSingleton<Test>;
创建类实例的别名,以方便使用。
问题:
double-checked singleton实现依然存在线程安全问题。
如 m_pInstance = new T();
这条代码包含了三个指令步骤:
1、分配内存;2、在内存位置上调用构造函数;3、将内存地址赋值给m_pInstance。
volatile关键字并不能组织CPU的乱序执行。
由于CPU的动态调度(乱序执行不相关的指令),步骤2和3顺序不相关,可能3早于2执行。即m_pInstance先得到地址,而此时对象还没有构造(完成)。<span style="font-family: Arial, Helvetica, sans-serif;">如果这时有另一线程调用Instance()函数,则会直接返回m_pInstance,而造成获取到不完整的对象。</span>