【Singleton模式】C++设计模式——单例模式

单例模式

  • 一、设计流程探讨
  • 二、模式介绍
  • 三、代码实现
  • 四、reorder问题重述

    C++设计模式大全,23种设计模式合集详解—(点我跳转)

一、设计流程探讨

  单例模式的结构非常简单,如下类图所示。在单例模式种,需要注意多线程的问题,多线程会导致不一定遵循单例,本文将在代码实现种进行讨论。首先在该模式种,我们需要将构造函数、拷贝构造函数设置为私有的,如果不这么做C++编译器将会默认给你生产公有的构造函数,这样外界就可以访问到这构造函数了,然后设置静态的变量。
【Singleton模式】C++设计模式——单例模式_第1张图片
  在了解单例模式之前,建议大家先去了解下C++各种锁的知识,有助于理解单例模式中的安全锁的问题。

二、模式介绍

(1)模式动机
  在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、良好的效率。
  如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?这应该是类设计者的责任,而不是使用者的责任。
(2)模式定义
  保证一个类仅有一个实例,并提供一个该实例的全局访问点。
(3)要点总结
a). Singleton模式中的实例构造器一般设置为private,但也可以设置为protected以允许子类派生。
b). Singleton模式一般不要支持拷贝构造函数和Clone接口,因为这有可能导致多个对象实例,与Singleton模式的初衷违背。
c). 如何实现多线程环境下安全的Singleton?注意对双检查锁的正确实现。

三、代码实现

class Singleton{
private:
	Singleton();
	Singleton(const Singleton& other);
	static Singleton* m_instance;
	static mutex m_mutex;
	
public:
	static Singleton* getInstance();
};

Singleton* Singleton::m_instance = nullptr;
// 线程非安全版本
/**
 * 当多线程同时进入时,此时m_instance都是nullptr,
 * 所以其他线程也获取了这个实例,并不符合单例
 */
Singleton* Singleton::getInstance(){
	if (m_instance == nullptr) {
		m_instance = new Singleton();
	}
	return m_instance;
}
// 线程安全版本,但锁的代价过高
/**
 * 因为有锁,所以得等锁释放后其他线程才能进来访问,可以使多线程安全
 * 但是 m_instance 已经不是null了,如果是多个线程同时读一个变量,
 * 是不需加锁的,写操作才需要加锁。所以此时读操作,太多线程在没必要的等待着
 */
Singleton* Singleton::getInstance(){
	Lock lock;
	if (m_instance == nullptr) {
		m_instance = new Singleton();
	}
}
// 双检查锁,但由于内存读写reorder不安全
/**
 * 两次判断 m_instance 是否为空,是因为在lock之前,A线程准备执行下一个,
 * 但B线程同时进来也在lock等待着,如果不双重检查,就会出现两个线程分别
 * 获得两个实例。但是这机制有问题,因为在计算机上有reorder的问题,也就
 * 是说在计算机执行的开辟内存、执行构造函数、地址赋值时候顺序可能会被打乱
 */
Singleton* Singleton::getInstance(){
	if (m_instance == nullptr) {
		Lock lock;
		if (m_instance == nullptr) {
			m_instance == new Singleton();
		}
	}
	return m_instace;
}
// C++11版本之后的跨平台实(Java、C#采用的是加volatile但不能跨平台)
atomic<Singleton*> Singleton::m_instance;
mutex Singleton::m_mutex;
Singleton* Singleton::getInstance(){
	Singleton* tmp = m_instance.load(memory_order_relaxed);
	atomic_thread_fence(memory_order_acquire);		//fence是屏障,获取内存的fence
	if (tmp == nullptr) {
		lock_guard<mutex> lock(m_mutex);
		tmp = m_instance.load(memory_order_relaxed);
		if (tmp == nullptr) {
			tmp = new Singleton;
			atomic_thread_fence(memory_order_release);	//释放内存的fence
			m_instance.store(tmp, memory_order_relaxed);
		}
	}
	return tmp;
}

四、reorder问题重述

比如在进行m_instance = new Singleton()的时候,我们期望的是这三步:
  ①先为 m_instance 分配内存
  ②然后调用 Singleton() 方法构造实例
  ③最后赋值 m_instance 实例引用
然而编译器优化可能会将 ② ③ 步的顺序调换,这样重排序并不影响单线程的执行结果,JVM是允许的。但是在多线程中就会出问题,这时如果另外一个线程B 拿到了不为null 的instance实例引用,但是并没有被初始化,然后线程B使用了一个没有被初始化的对象引用,就会产生严重的错误,而且这个出现的频率还蛮高。

你可能感兴趣的:(C++,单例模式,c++,设计模式)