@著作权归作者所有:来自CSDN博客作者大胡子的艾娃的原创作品,如需转载,请注明出处https://blog.csdn.net/qq_43148810,否则将追究法律责任。
如有错误的地方欢迎指正,谢谢!

一、单例应用场景

1、应用程序的日志
2、应用的配置
3、多线程的线程池
4、内存池
等等
联想:和单例相类似的有“创建后不能拷贝,只能move”,标准库中有thread、mutex、lock_guard等

二、非常经典的的两个版本

1、懒汉版本(static对象)

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

2、饿汉版本(new pointer)

class Singleton
{private:Singleton() = default;Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;~Singleton() = default;
private:static Singleton* m_instance;
public:static Singleton* getInstance(){return m_instance;}
};
Singleton* Singleton::m_instance = new Singleton;

三、饿汉版本(new pointer)的多线程探讨

1、多线程非安全版本

class Singleton
{private:Singleton() = default;Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;~Singleton() = default;
public:static Singleton* getInstance();
private:static Singleton* m_instance;
};
Singleton* Singleton::m_instance = nullptr;Singleton* Singleton::getInstance()
{if (m_instance == nullptr){m_instance = new Singleton();}return m_instance;
}

非安全原因:多线程都同时第一次进入getInstance()函数对m_instance 进行空判,但都未创建对象,那么都会去new Singleton。

2、多线程安全版本,但锁的代价过高,不适合高并发和高频率获取

class Singleton
{private:Singleton() = default;Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;~Singleton() = default;public:static Singleton* getInstance();private:static std::mutex m_mutexSingleton;static Singleton* m_instance;
};Singleton* Singleton::m_instance = nullptr;
std::mutex Singleton::m_mutexSingleton;Singleton* Singleton::getInstance()
{//std::lock_guard<std::mutex> guard(m_mutexSingleton);m_mutexSingleton.lock();if (m_instance == nullptr) {m_instance = new Singleton();}m_mutexSingleton.unlock();return m_instance;
}

为什么代价过高:多个线程,只要有一个线程走完锁的部分,就会new Singleton,完成“写”操作,那么后面所有线程都只有“读”操作,就不会存在线程安全问题。单例本身就只创建一个,而多次被读取,读的过程常锁带来性性能损失过大。

3、存在内存读写reorder不安全的双检查锁
除getInstance()函数和上一示例(2)不一样,其他部分都相同。

Singleton* Singleton::getInstance()
{if (m_instance == nullptr){//std::lock_guard<std::mutex> guard(m_mutexSingleton);m_mutexSingleton.lock();if (m_instance == nullptr){m_instance = new Singleton();}m_mutexSingleton.unlock();}return m_instance;
}

表面上看解决了上一版本中加锁导致的性能问题,看起来很完美,甚至被写入教科书,但是确实存在问题。reorder会导致双检查锁失效。“m_instance = new Singleton();”这段代码由于可以被某些编译器未知因素(操作系统、硬件、其他线程等)被编译器优化。实现可能有两种情况:1、先分配内存,再调用构造去初始化这块内存,最后将m_instance指针指向这块内存;2、先分配内存,再将m_instance指针指向这这段内存,最后调用构造去初始化这块内存。reorder就是是指1和2两种不定的情况。如果是情况1是双检查锁是没有问题;但是出现情况2就会出现第一个进程走到只对m_instance分配内存并将指针指向该内存,但还未构造初始化该内存,后面的线程此时如果走到第一个检查的空判,就会返回指向未经过构造的m_instance指针。此时reorder就会导致双检查锁失效。
【备注】有的编程语言不会出现情况2,也有的非标C++有关键字(volatile)声明来处理reorder,const和指针都可以是volatile。

4、利用std::once_flag类和std::call_once函数保证创建单例只执行一次,而每次获取单例对:call_once判断。
极力推荐使用该方法

class Singleton
{private:Singleton() = default;Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;~Singleton() = default;class CSingleton{public:~CSingleton(){if (Singleton::m_instance){delete Singleton::m_instance;Singleton::m_instance = nullptr;}}};static void CreateInstance(){std::cout << "首次执行CreateInstance!\n";static CSingleton CS;m_instance = new Singleton();}
private:static Singleton* m_instance;static std::once_flag m_flag;
public:static Singleton* getInstance(){std::call_once(m_flag, CreateInstance);return m_instance;}
};
Singleton* Singleton::m_instance = nullptr;
std::once_flag Singleton::m_flag{};

5、C++11版本之后跨平台实现

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);//获取内存fenceif (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);//释放内存fencem_instance.store(tmp, std::memory_order_relaxed);}}return tmp;
}

6、细心的会发现除4中其他的饿汉模式有new,但是没有delete,交给系统回收这段内存了,并不会产生什么问题。
通过代码回收的方式如下(未考虑多线程)。

在构造函数内创建类内静态对象CS,程序退出回收静态对象CS,利用CS的析构来释放单例。

class Singleton
{private:class CSingleton{public:~CSingleton(){if (Singleton::m_instance){delete Singleton::m_instance;Singleton::m_instance = nullptr;}}};
...
public:static Singleton* getInstance(){static CSingleton CS;return m_instance;}
};

四、总结

1、只能生成一个实例的类,实现了Singleton(单例)模式的类型。
2、条件1中具体要求表现为构造函数和析构函数私有,不允许有拷贝构造和拷贝函数。
3、由静态成员函数获取Singleton(单例)对象。
4、多线程安全。
5、若为饿汉式,创建单例对象后,不存在线程安全问题,考虑的性能问题。
6、reorder问题会导致经典双检查锁C++实现失效。

如有错误或不足欢迎评论指出!创作不易,转载请注明出处。如有帮助,记得点赞关注哦(⊙o⊙)
更多内容请关注个人博客:https://blog.csdn.net/qq_43148810

创建型模式Creational Patterns之单例模式singleton相关推荐

  1. 创建型模式(五):Singleton(单例模式)

    单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例模式.单例模式只应在有真正的"单一实例"的需求时才可使用. 1 class Singleton { 2 ...

  2. Java设计模式之五大创建型模式

    Java设计模式之五大创建型模式 设计模式(23种) 单例模式(Singleton Pattern) 参考链接 概念 使用场景 实现思路 实现方式 饿汉式(静态常量) 饿汉式(静态代码块) 懒汉式(线 ...

  3. 软件设计模式--第二章 创建型模式-- 单例模式

    目录 第二章 创建型模式 1.创建型模式概述 2.单例模式(Singleton) (1)模式的定义与特点 (2)模式的结构与实现 (3)应用场景 (4)扩展 第二章 创建型模式 1.创建型模式概述 主 ...

  4. JAVA设计模式 - 创建型模式总结

    JAVA设计模式 - 单例模式 JAVA设计模式 - 工厂方法模式 JAVA设计模式 - 抽象工厂模式 JAVA设计模式 - 原型模式 JAVA设计模式 - 建造者模式 1 . 创建型模式的定义 顾名 ...

  5. Singleton patterns 单件(创建型模式)

    1.模式分类 1.1  从目的来看: •      – 创建型(Creational)模式:负责对象创建. •      – 结构型(Structural)模式:处理类与对象间的组合. •      ...

  6. 设计模式教程(Design Patterns Tutorial)笔记之一 创建型模式(Creational Patterns)...

    设计模式教程(Design Patterns Tutorial)笔记之一 创建型模式(Creational Patterns) 目录 · 概述 · Factory · What is the Fact ...

  7. 创建型模式--(再论)单例模式

    在 创建型模式--单例模式 一文中我们提到单例模式,就是 一个类仅有一个实例,并提供一个访问它的全局访问点. 那么问题来了,我们以上一篇中代码为例: <pre name="code&q ...

  8. 3创建型模式之单例模式

    概念 单例模式是一种对象创建型模式,使用单例模式,可以保证为一个类只生成唯一的实例对象.也就是说,在整个程序空间中,该类只存在一个实例对象. GoF对单例模式的定义是:保证一个类.只有一个实例存在,同 ...

  9. 创建型模式:单例模式(懒汉+饿汉+双锁校验+内部类+枚举)

    单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 这种模式涉及到一个单一的类,该类负责创 ...

  10. C#面向对象设计模式纵横谈——Singleton单件(创建型模式)

    Singleton单件(创建型模式) 动机(Motivation) 在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性.以及良好的效率. 如何绕过常规的 ...

最新文章

  1. 智源论坛Live丨青年科学家线上报告会日程发布
  2. oracle numtodsinterval and numtoyminterval 使用法则
  3. 一文看透 Redis 分布式锁进化史(解读 + 缺陷分析)
  4. Nginx 反向代理、负载均衡、页面缓存、URL重写及读写分离详解(1)
  5. nsct matlab,图像融合 NSCT算法 matlab
  6. 我的世界服务器显示不出地图,为什么我的世界服务器地图加载不了
  7. Storm编程模型总结
  8. 大咖说中台 | 建设数据中台系列(五)——中台架构详解(下)
  9. Linux环境编程导引
  10. 译文丨伯克利对serverless的看法:简化云编程
  11. RTSP,RTP,RTCP的区别
  12. bootstrap 3.0 LESS源代码浅析(一)
  13. 窥探源码,让我更加优雅的使用Kafka生产者!
  14. 晶体管制程极限之后,多层CPU是否可能?
  15. sas数据集怎么导出_SAS:将proc步的输出导出为数据集
  16. 国标28181:libexosip2协议栈原理
  17. 电脑分屏没有声音_电脑分屏声音怎么分
  18. 1563页Go语言中文文档,涵盖Go语言所有核心知识点
  19. 华为路由器时间同步_华为路由器肿么和互联网时间同步
  20. windows10---Excel2016基本操作技巧

热门文章

  1. 九、【服务器】服务器硬件名称中英文汇总2
  2. 网络带宽和下载速度的换算方法
  3. sersync+rsync的热备份研究(热备+断点续传)
  4. CSDN博客排版格式入门
  5. modbus软件开发linux,Linux C开发之用modbus实现串口通讯
  6. Word 之 清除页眉下划线
  7. 测试用例八大要素及额外十小要素
  8. 计算机组成部件及其作用,计算机硬件组成成分各自的功能 计算机硬件有哪几部分组成?请分别说明各部分的作用?...
  9. numpy_10 思维导图
  10. 【大话设计模式-11】组合模式(案例解析)