设计模式|单例模式4种实现方式C++

单例模式:保证一个类只有一个实例,并提供一个该实例的全局访问点
实现方式:构造和拷贝构造设为私有,总共介绍四个版本,推荐使用最后一个。版本1、2、3、4全都是懒汉式的写法。
版本1:线程非安全版本 在多线程情况下可能会同时创建出多个对象,比如,现在有线程A和线程B,线程A进入到16行时, 线程B进入17行,会容易new出多个实例。
设计模式|单例模式4种实现方式C++
文章图片
版本2:线程安全,但锁的代价过高 在GetInstance()中使用局部变量锁, 保证同一时刻只有一个线程访问30-33行。
读变量没必要加锁,尤其是在高并发的情况下,代价还是挺高的。
设计模式|单例模式4种实现方式C++
文章图片

版本3:双检查锁,但由于内存读写reorder(重新排序)不安全(导致双检查锁的失效) 锁前检查,避免都是读取操作时锁代价过高的问题
锁后检查,避免两个线程同时进入,从而new了两个实例
设计模式|单例模式4种实现方式C++
文章图片
因为编译器优化,指令的执行顺序可能reorder(CPU执行指令的层次,而且线程是在指令层次抢时间片的) ,可能变成这样:分配内存->赋值->调用构造(理想应该是:分配内存->调用构造->赋值)
假设在reorder的情况下:线程1走到赋值,但还没调用构造的阶段,而线程2进来判断m_instance,此时它已经被复制 所以不为空,这时候线程2就直接返回m_instance,但事实上它还没构造出来……这就尴尬了,实际上因为它没有构造,肯定是不能用的。
总之,就是双检查锁 它欺骗了线程2……
怎么解决这个问题呢?
Java 和c sharp 添加了一个关键字:volatile
这样,编译时在编译的时候就知道,这个变量的整个赋值过程不能reorder,需要按照常规的流程走。
C++11之后 跨平台实现了volatile
还是挺复杂的哈……
设计模式|单例模式4种实现方式C++
文章图片

前3个版本均来自侯捷老师的视频,可以去观看一下,讲的灰常简单易懂
版本4:局部静态变量实现单例模式,线程安全(推荐使用)

class Singleton{ public: ~Singleton(); Singleton& (const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; static Singleton& getInstance() { static Singleton instance; return instance; } private: Singleton(); };

【设计模式|单例模式4种实现方式C++】原因是C++ 11标准中新增了一个特性叫Magic Static:如果变量在初始化时,并发线程同时进入到static声明语句,并发线程会阻塞等待初始化结束。
这样可以保证在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性,同时也避免了new对象时指令重排序造成对象初始化不完全的现象。并且相比较与使用智能指针以及mutex来保证线程安全和内存安全来说,这样做能够提升效率。

    推荐阅读