概念
单例模式是一种对象创建型模式,使用单例模式,可以保证为一个类只生成唯一的实例对象。也就是说,在整个程序空间中,该类只存在一个实例对象。
动机
在软件系统中,经常有这样一些特殊的类,必须保证他们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。
如何绕过常规的构造器,提供一种机制来确保一个类只有一个实例?
保证一个类、只有一个实例存在,同时提供能对该实例加以访问的全局访问方法。
为什么使用单例模式
在应用系统开发中,我们常常有以下需求:
- 在多个线程之间,比如初始化一次socket资源;比如servlet环境,共享同一个资源或者操作同一个对象
- 在整个程序空间使用全局变量,共享资源
- 大规模系统中,为了性能的考虑,需要节省对象的创建时间等等。
因为Singleton模式可以保证为一个类只生成唯一的实例对象,所以这些情况,Singleton模式就派上用场了
实现单例步骤常用步骤
Singleton 模式其实是对全局静态变量的一个取代策略,上面提到的 Singleton 模式的两个作用在 C++中是通过如下的机制实现的:1)仅有一个实例,提供一个 类的静态成员变量,大家知道类的静态成员变量对于一个类的所有对象而言是惟 一的 2)提供一个访问它的全局访问点,也就是提供对应的访问这个静态成员变 量的静态成员函数,对类的所有对象而言也是惟一的.在C++中,可以直接使用类 域进行访问而不必初始化一个类的对象.
对于多线程下的单例模式,需要使用双检查锁,双检查锁的正确实现必须要注意内存的reorder
代码:
class Singleton{
private:
Singleton();
Singleton(const Singleton& other);
public:
static Singleton* getInstance();
static Singleton* m_instance;
};
Singleton* Singleton::m_instance=nullptr;
//线程非安全版本
Singleton* Singleton::getInstance() {
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {
Lock lock;
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
if(m_instance==nullptr){
Lock lock;
if (m_instance == nullptr) {
m_instance = new Singleton();
}
}
return m_instance;
}
//C++ 11版本之后的跨平台实现 (volatile)
std::atomic<Singleton*>
Singleton::m_instance;
std::mutex Singleton::m_mutex;
Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(m_mutex);
tmp =m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
std::atomic_thread_fence(std::memory_order_release);//释放内存fence
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
懒汉式的Double-Check是一个经典问题!为什么需要2次检查 “if(pInstance == NULL)”
场景:假设有线程1、线程2、线程3,同时资源竞争。
//1)第1个、2个、3个线程执行第一个检查,都有可能进入黄色区域(临界区)
//2)若第1个线程进入到临界区,第2个、第3个线程需要等待
//3)第1个线程执行完毕,cs.unlock()后,第2个、第3个线程要竞争执行临界区代码。
//4)假若第2个线程进入临界区,此时第2个线程需要再次判断
if(pInstance == NULL)”,若第一个线程已经创建实例;第2个线程就不需要再次创建了。保证了单例;
//5)同样道理,若第2个线程,cs.unlock()后,第3个线程会竞争执行临界区代码;此时第3个线程需要再次判断 if(pInstance == NULL)。通过检查发现实例已经new出来,就不需要再次创建;保证了单例。