1、前言

单例模式属于创建型模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。

单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。


2、介绍

2.1、主要解决

防止一个系统全局使用的类频繁地创建与销毁、解决多线程并发访问的问题和节约系统内存等,提高系统运行的效率,提高系统性能。

什么情况需要使用全局的类?通常是对共享资源的使用。

比如需要实现系统控制打印机工作,一般都会定义一个“打印机管理类”用来管理打印机的各个功能,有多个模块都需要控制打印机工作,在没有使用单例模式的情况下,会遇到一下问题:

  1. 模块会在需要打印时创建“打印机管理类”,打印完成即销毁,如果需要频繁打印,那么就会频繁地创建和销毁该类;
  2. 如果打印完成不销毁呢?那么多个模块都会创建“打印机管理类”,浪费系统资源;
  3. 一旦出现多个模块同时需要打印,就要解决各模块之间的打印同步问题,毕竟打印机只有一个。

使用单例模式后:

  1. 单例模式会自行创建,同时只提供一个全局访问的类,因此不会造成系统资源的浪费,因此也没有了频繁地创建和销毁的需求。
  2. 多个模块同时需要打印时,只需要由全局的“打印机管理类”实例对打印的任务进行同步管理,无需多个模块之间解决同步的问题,提高系统运行的效率。

2.2、优缺点

单例模式的使用需要根据实际情况使用,以下是该设计模式的优缺点。

优点:

  • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例;
  • 避免了多线程并发访问时只需要该类管理同步即可,提高系统运行的效率,提高系统性能。

缺点:

  • 没有接口,不能继承,与单一职责原则冲突;
  • 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。

3、实现

在了解单例模式可以解决什么问题的情况下,那么如何实现一个单例模式呢?

  1. 在类中添加一个该类的私有静态成员变量用于保存单例实例。
  2. 声明一个公有静态构建方法用于获取单例实例。
  3. 将类的构造函数设为私有。

3.1、懒汉设计

顾名思义,不到万不得已就不会去实例化类,对象只有被调用时才去创建,这种方式是最基本的实现方式,但这种实现最大的问题就是不支持多线程,属于线程不安全的,在多线程使用中,容易多次创建实例,因此严格的来说,并不算单例模式。

class CPrinter
{
public:
static CPrinter *GetInstance() // 提供全局访问实例节点
{
if (ms_pinstance == nullptr)
{
ms_pinstance = new CPrinter();
} return ms_pinstance;
} private:
CPrinter(){}
~CPrinter(){} private:
static CPrinter *ms_pinstance; public:
void Work(void)
{
printf("[%p]Printer working\n", this);
}
}; CPrinter *CPrinter::ms_pinstance = nullptr; int main()
{
CPrinter::GetInstance()->Work(); return 0;
}

为了解决在多线程使用中,容易多次创建实例的问题,可以加上互斥锁,这样就保证了线程安全。

#include <iostream>
#include <thread>
#include <mutex> using namespace std; class CPrinter
{
public:
static CPrinter *GetInstance() // 提供全局访问实例节点
{
ms_initMutex.lock(); if (ms_pinstance == nullptr)
{
ms_pinstance = new CPrinter();
} ms_initMutex.unlock(); return ms_pinstance;
} private:
CPrinter(){}
~CPrinter(){} private:
static CPrinter *ms_pinstance;
static std::mutex ms_initMutex; public:
void Work(void)
{
printf("[%p]Printer working\n", this);
}
}; // 类外初始化
CPrinter *CPrinter::ms_pinstance = nullptr;
std::mutex CPrinter::ms_initMutex; int main()
{
/* 创建线程1 */
std::thread([=]{
CPrinter::GetInstance()->Work();
}).detach(); /* 创建线程2 */
std::thread([=]{
CPrinter::GetInstance()->Work();
}).detach(); return 0;
}

3.2、懒汉设计+双重校验锁

虽然上述的优化后的懒汉设计实现解决了线程安全的问题,但加锁会影响效率,为了解决线程安全的问题,同时提高效率,采用双重检查加锁机制,先判断是否为 nullptr,如果不为空,则直接返回实例,避免加锁影响效率,而加锁之后再判断,是为了防止在加锁之前多个线程都进入且竞争互斥锁,避免下一个线程获取锁后再次创建实例。

#include <iostream>
#include <thread>
#include <mutex> using namespace std; class CPrinter
{
public:
static CPrinter *GetInstance() // 提供全局访问实例节点
{
if (ms_pinstance == nullptr)
{
ms_initMutex.lock(); // 如果为空则加锁 if (ms_pinstance == nullptr)
{
ms_pinstance = new CPrinter();
} ms_initMutex.unlock();
} return ms_pinstance;
} private:
CPrinter(){}
~CPrinter(){} private:
static CPrinter *ms_pinstance;
static std::mutex ms_initMutex; public:
void Work(void)
{
printf("[%p]Printer working\n", this);
}
}; // 类外初始化
CPrinter *CPrinter::ms_pinstance = nullptr;
std::mutex CPrinter::ms_initMutex; int main()
{
/* 创建线程1 */
std::thread([=]{
CPrinter::GetInstance()->Work();
}).detach(); /* 创建线程2 */
std::thread([=]{
CPrinter::GetInstance()->Work();
}).detach(); return 0;
}

3.3、饿汉设计

饿汉模式就是在单例类定义的时候(即在main函数之前)就进行实例化。因为main函数执行之前,全局作用域的类成员静态变量ms_instance 已经初始化,因此就不存在多线程的问题。

#include <iostream>
#include <thread> using namespace std; class CPrinter
{
public:
static CPrinter *GetInstance() // 提供全局访问实例节点
{
return &ms_instance;
} private:
CPrinter(){}
~CPrinter(){} private:
static CPrinter ms_instance; public:
void Work(void)
{
printf("[%p]Printer working\n", this);
}
}; CPrinter CPrinter::ms_instance; int main()
{
std::thread([=]{
CPrinter::GetInstance()->Work();
}).detach(); std::thread([=]{
CPrinter::GetInstance()->Work();
}).detach(); return 0;
}

设计模式 - 创建型模式 - 单例模式(C++)的更多相关文章

  1. java设计模式--创建型模式(一)

    2016-04-24 10:10:34 创建型模式:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式 注意:工厂模式可以分为三类: 1)简单工厂模式(Simple Factory) 2)工厂 ...

  2. C# 设计模式·创建型模式

    面试问到这个··答不出来就是没有架构能力···这里学习一下···面试的时候直接让我说出26种设计模式··当时就懵逼了··我记得好像之前看的时候是23种的 还有3个是啥的··· 这里先列出几种创建型模式 ...

  3. [19/04/22-星期一] GOF23_创建型模式(单例模式)

    一.概念 <Design Patterns: Elements of Reusable Object-Oriented Software>(即后述<设计模式>一书),由 Eri ...

  4. C#设计模式-创建型模式(转)

    一.简单工厂模式 简单工厂模式Simple Factory,又称静态工厂方法模式.它是类的创建模式.是由一个工厂对象决定创建出哪一种产品类的实例,是不同的工厂方法模式的一个特殊实现. 优点: u 模式 ...

  5. 创建型模式 - 单例模式Singleton

    单例模式的定义与特点 创建型模式:         单例模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式. 例如,Windows中只能打开一个任务管理器,这样可以避免因打开多个任务 ...

  6. 设计模式01 创建型模式 - 单例模式(Singleton Pattern)

    参考 [1] 设计模式之:创建型设计模式(6种) | 博客园 [2] 单例模式的八种写法比较 | 博客园 单例模式(Singleton  Pattern) 确保一个类有且仅有一个实例,并且为客户提供一 ...

  7. 设计模式-创建型模式,python享元模式 、python单例模式(7)

    享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能.这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式. 享元模式尝 ...

  8. python设计模式---创建型之单例模式

    数据结构和算法是基本功, 设计模式是最佳实现. 作为程序员,必须有空了就练一练哈. # coding = utf-8 """ # 经典单例 class Singleton ...

  9. 设计模式----创建型模式之工厂模式(FactoryPattern)

    工厂模式主要分为三种简单工厂模式.工厂方法模式.抽象工厂模式三种.顾名思义,工厂主要是生产产品,作为顾客或者商家,我们不考虑工厂内部是怎么一个流程,我们要的是最终产品.将该种思路放在我们面向对象开发实 ...

  10. 【C#设计模式——创建型模式】简单工场模式

    进入码农行列也有一年半载了,仍然感觉自己混混沌沌,无所事事,无所作为,,,想想都下气,下气归下气,仍要奋起潜行,像愤怒的小鸟一边又一遍的冲向猪头也好,像蜗牛一样往前蹭也罢,总之要有蚂蚁啃骨头的精神!! ...

随机推荐

  1. shell脚本(16)-awk命令

    文档目录 一.awk介绍 二.awk基本用法 1.awk对字段(列)的提取: 2.awk对记录(行)的提取: 3.awk对字符串提取: 4.awk程序的优先级: 三.awk高级用法 1.awk定义数组 ...

  2. python进阶(5)--函数

    文档目录: 一.函数体二.实参与形参三.返回值四.举例:函数+while循环五.举例:列表/元组/字典传递六.模块与函数的导入 ------------------------------------ ...

  3. JVM 性能调优 及 为什么要减少 Full GC

    本文为博主原创,未经允许不得转载: 系统上线压测,需要了解系统的瓶颈以及吞吐量,并根据压测数据进行对应的优化. 对压测进行 JVM 性能优化,有两条思路: 第一种情况 : 使用压测工具 jmeter  ...

  4. 星索称重/生产管理软件 联机版V1.0

    星索称重/生产管理软件 联机版V1.0   一.特点 1.支持多用户.多组织管理,灵活控制用户权限. 2.支持地磅秤.智能电子秤.轨道秤等多款称重设备. 3.支持三联单/热敏纸等多种打印模板. 二.系 ...

  5. css : object-fit 兼容 ie 的解决方案

    通过 github 搜索 object-fit ie  ,  借鉴大佬兼容 ie 的经验. 下载解压到文件夹 , 打开测试目录 , 查看 demo 使用 ie 打开demo , 查看显示效果 : 代码 ...

  6. phpcms : Uncaught Error: [] operator not supported for strings... 的解决方案

    打开/phpcms/modules/admin/classes/push_api.class.php,大概在约 141行, $fields_arr = $fields_value = ''; 将它改为 ...

  7. 在TypeScript项目中搭配Axios封装后端接口调用

    前言 本来是想发 next.js 开发笔记的,结果发现里面涉及了太多东西,还是拆分出来发吧~ 本文记录一下在 TypeScript 项目里封装 axios 的过程,之前在开发 StarBlog-Adm ...

  8. [转帖]聊聊TPS、QPS、CPS概念和区别

    https://cloud.tencent.com/developer/article/1859053 TPS 概念 TPS:是TransactionsPerSecond的缩写,也就是事务数/秒.它是 ...

  9. [转帖]Jmeter学习笔记(十九)——后置处理器之正则表达式的使用

    https://www.cnblogs.com/pachongshangdexuebi/p/11733005.html 一.正则表达式提取器的作用 允许用户从服务器的响应中通过使用perl的正则表达式 ...

  10. [转帖]《Linux性能优化实战》笔记(24)—— 动态追踪 DTrace

    使用 perf 对系统内核线程进行分析时,内核线程依然还在正常运行中,所以这种方法也被称为动态追踪技术.动态追踪技术通过探针机制来采集内核或者应用程序的运行信息,从而可以不用修改内核和应用程序的代码就 ...