如何设计一个正确的单例模式类
一、大家一般创建的设计单例类
demo 1:
class A
{
public:
static A* GetSingleInstance()
{
//步骤1
if(m_instance == NULL)
{
//步骤2
m_instance = new A();
}
return m_instance;
}
private: //构造函数和变量声明为private,如果是基类希望被人继承使用,则声明为protect
A(){};
static A* m_instance;
}
A* A::m_instance = NULL;
如上demo所示,这是大家习惯的正常创建单例类的方式。当他仅被使用於单线程,则完全没问题。但如果用于多线程时,则可能会出现重复new对象的问题!
二、解决多线程访问单例重复new内存问题
当多线程访问“demo 1”中的单例时,此时获取单例的函数为非线程安全的,所以,会导致多线程异步执行时,出现重复new 对象的问题,这时可以为其加锁的方式解决,如下demo 2所示:
demo 2:
class A
{
public:
static A* GetSingleInstance()
{
std::mutex mt;
mt.lock();
//步骤1
if(m_instance == NULL)
{
//步骤2
m_instance = new A();
}
mt.unlock();
return m_instance;
}
private: //构造函数和变量声明为private,如果是基类希望被人继承使用,则声明为protect
A(){};
static A* m_instance;
}
A* A::m_instance = NULL;
上述代码中,相当于给获取单例函数加了一个函数全局锁,使该函数为线程安全函数。但如果是在高并发场景下,这个锁的性能消耗是很大的!因为除了第一次真正创建实例外,其余的操作都是“读”操作,所以,需要对这个demo再进行改进。
三、使用双检查锁机制,但由于内存reorder机制使线程不安全
根据“demo 2”中锁描述的方式,则应该在第一次创建实例后,后续获取实例时尽量不使用锁,这样就能解决高并发场景下锁带来的性能问题。实现方式如下所示:
demo 3:
class A
{
public:
static A* GetSingleInstance()
{
//步骤1
if(m_instance == NULL)
{
std::mutex mt;
mt.lock();
if(m_instance == NULL)
{
//步骤2
m_instance = new A();
}
mt.unlock();
}
return m_instance;
}
private: //构造函数和变量声明为private,如果是基类希望被人继承使用,则声明为protect
A(){};
static A* m_instance;
}
A* A::m_instance = NULL;
如“demo 3” 所示,使用双检查锁机制能有效的减少在高并发场景下因为锁带来的性能消耗,但是由于多线程在操作系统指令级别处理时,有可能会产生与代码的顺序不一致的问题,即new对象首先分配一段内存,然后返回地址,然后再初始化,这时就会导致其他线程拿到了一个没有初始化的对象而产生后续的一些问题,这就是“Memory reorder”现象,详情参考“https://www.cnblogs.com/cbscan/articles/4122337.html”。所以,使用“demo 3”版本的双检查锁是线程不安全的,大家不要去使用!!
四、C++11彻底解决多线程下锁消耗问题(这才是正解!!!)
更新后的demo如下图所示:
demo 3:
class A
{
public:
static A* GetSingleInstance()
{
***//C++11 版本之后的跨平台实现(Jave用volatile)***
A* temp = m_instance.load(std::memory_order_relaxed);
std::atomic_trhead_fence(std::memory_order_acquire); //获取内存fence
if(tmp == NULL)
{
std::lock_guard<std::mutex> lock(m_mt);
tmp = m_instance.load(std::memory_order_relaxed);
if(tmp == NULL)
{
tmp = new A();
std::atomic_trhead_fence(std::memory_order_release); //获取内存fence
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return m_instance;
}
private: //构造函数和变量声明为private,如果是基类希望被人继承使用,则声明为protect
A(){};
static std::atomic<A*> m_instance;
static std;:mutex m_mt;
}
std::atomic<A*> A::m_instance = NULL;
std::mutex A::m_mt;