【11】c++设计模式——>单例模式

单例模式是什么

在一个项目中,全局范围内,某个类的实例有且仅有一个(只能new一次),通过这个唯一的实例向其他模块提供数据的全局访问,这种模式就叫单例模式。单例模式的典型应用就是任务队列。

为什么要使用单例模式

单例模式充当的就是一个全局变量,为什么不直接使用全局变量呢,因为全局变量破坏类的封装,而且不受保护,访问不受限制。

在这里插入图片描述

饿汉模式

饿汉模式是在类加载时进行实例化的。

#include<iostream>
using namespace std;class TaskQueue
{
public:TaskQueue(const TaskQueue& obj) = delete;//禁用拷贝构造TaskQueue& operator = (const TaskQueue& obj) = delete;//禁用赋值构造static TaskQueue* getInstance()  //获取单例的方法{cout << "我是一个饿汉模式单例" << endl;return m_taskQ;}private:TaskQueue() = default; //无参构造static TaskQueue* m_taskQ; //静态成员需要在类外定义
};
TaskQueue* TaskQueue::m_taskQ = new TaskQueue;//new一个实例;int main()
{TaskQueue* obj = TaskQueue::getInstance();
}

懒汉模式

懒汉模式是在需要使用的时候再进行实例化

#include<iostream>
using namespace std;class TaskQueue
{
public:TaskQueue(const TaskQueue& obj) = delete;//禁用拷贝构造TaskQueue& operator = (const TaskQueue& obj) = delete;//禁用赋值构造static TaskQueue* getInstance()  //获取单例的方法{if (nullptr == m_taskQ){m_taskQ = new TaskQueue;}return m_taskQ;}private:TaskQueue() = default; //无参构造static TaskQueue* m_taskQ; //静态成员需要在类外定义
};
TaskQueue* TaskQueue::m_taskQ = nullptr;int main()
{TaskQueue* obj = TaskQueue::getInstance();
}

在调用**getInstance()**函数获取单例对象的时候,如果在单线程情况下是没有什么问题的,如果是多个线程,调用这个函数去访问单例对象就有问题了。假设有三个线程同时执行了getInstance()函数,在这个函数内部每个线程都会new出一个实例对象。此时,这个任务队列类的实例对象不是一个而是3个,很显然这与单例模式的定义是相悖的。

线程安全问题

对于饿汉模式来说是没有线程安全问题的,在这种模式下访问单例对象时,这个对象已经被创建出来了,要解决懒汉模式的线程安全问题,最常用的解决方案就是使用互斥锁,可以将创建单例对象的代码使用互斥锁锁住:

#include<iostream>
#include<mutex>
using namespace std;class TaskQueue
{
public:TaskQueue(const TaskQueue& obj) = delete;//禁用拷贝构造TaskQueue& operator = (const TaskQueue& obj) = delete;//禁用赋值构造static TaskQueue* getInstance()  //获取单例的方法{m_mutex.lock();if (nullptr == m_taskQ){cout << "我加了互斥锁" << endl;m_taskQ = new TaskQueue;}m_mutex.unlock();return m_taskQ;}private:TaskQueue() = default; //无参构造static TaskQueue* m_taskQ; //静态成员需要在类外定义static mutex m_mutex; //定义为静态的,因为静态函数只能使用静态变量
};
mutex TaskQueue::m_mutex;
TaskQueue* TaskQueue::m_taskQ = nullptr;int main()
{TaskQueue* obj = TaskQueue::getInstance();
}

在上面代码的10~13 行这个代码块被互斥锁锁住了,也就意味着不论有多少个线程,同时执行这个代码块的线程只能是一个(相当于是严重限行了,在重负载情况下,可能导致响应缓慢)。我们可以将代码再优化一下:

#include<iostream>
#include<mutex>
using namespace std;class TaskQueue
{
public:TaskQueue(const TaskQueue& obj) = delete;//禁用拷贝构造TaskQueue& operator = (const TaskQueue& obj) = delete;//禁用赋值构造static TaskQueue* getInstance()  //获取单例的方法{if (nullptr == m_taskQ){m_mutex.lock();if (nullptr == m_taskQ){cout << "我加了互斥锁" << endl;m_taskQ = new TaskQueue;}m_mutex.unlock();}return m_taskQ;}private:TaskQueue() = default; //无参构造static TaskQueue* m_taskQ; //静态成员需要在类外定义static mutex m_mutex;
};
mutex TaskQueue::m_mutex;
TaskQueue* TaskQueue::m_taskQ = nullptr;int main()
{TaskQueue* obj = TaskQueue::getInstance();
}

双重检查锁定问题

#include<iostream>
#include<mutex>
#include<atomic>
using namespace std;class TaskQueue
{
public:TaskQueue(const TaskQueue& obj) = delete;//禁用拷贝构造TaskQueue& operator = (const TaskQueue& obj) = delete;//禁用赋值构造static TaskQueue* getInstance()  //获取单例的方法{TaskQueue* taskQ = m_taskQ.load(); //取出来单例的值if (nullptr == taskQ){m_mutex.lock();taskQ = m_taskQ.load();if (nullptr == taskQ){cout << "我加了原子" << endl;taskQ = new TaskQueue;m_taskQ.store(taskQ); //保存到原子变量中}m_mutex.unlock();}return m_taskQ.load();}private:TaskQueue() = default; //无参构造static atomic<TaskQueue*>m_taskQ; //定义为原子变量static mutex m_mutex;
};
mutex TaskQueue::m_mutex;
atomic<TaskQueue*> TaskQueue::m_taskQ;int main()
{TaskQueue* obj = TaskQueue::getInstance();
}

对于m_taskQ = new TaskQueue这行代码来说,我们期望的执行机器指令执行顺序是:
(1)分配用来保存TaskQueue对象的内存;
(2)在分配好的内存中构造一个TaskQueue对象(即初始化内存);
(3)使用m_taskQ指针指向分配的内存。
但是对多线程来说,机器指令可能会被重新排列;即:
(1)分配内存用于保存 TaskQueue 对象。
(2)使用 m_taskQ 指针指向分配的内存。
(3)在分配的内存中构造一个 TaskQueue 对象(初始化内存)。
这样重排序并不影响单线程的执行结果,但是在多线程中就会出问题。如果线程A按照第二种顺序执行机器指令,执行完前两步之后失去CPU时间片被挂起了,此时线程B在第3行处进行指针判断的时候m_taskQ 指针是不为空的,但这个指针指向的内存却没有被初始化,最后线程 B 使用了一个没有被初始化的队列对象就出问题了(出现这种情况是概率问题,需要反复的大量测试问题才可能会出现)。
在C++11中引入了原子变量atomic,通过原子变量可以实现一种更安全的懒汉模式的单例,代码如下:

#include<iostream>
#include<mutex>
#include<atomic>
using namespace std;class TaskQueue
{
public:TaskQueue(const TaskQueue& obj) = delete;//禁用拷贝构造TaskQueue& operator = (const TaskQueue& obj) = delete;//禁用赋值构造static TaskQueue* getInstance()  //获取单例的方法{TaskQueue* taskQ = m_taskQ.load(); //取出来单例的值if (nullptr == taskQ){m_mutex.lock();taskQ = m_taskQ.load();if (nullptr == taskQ){cout << "我加了原子" << endl;taskQ = new TaskQueue;m_taskQ.store(taskQ); //保存到原子变量中}m_mutex.unlock();}return m_taskQ.load();}private:TaskQueue() = default; //无参构造static atomic<TaskQueue*>m_taskQ; //定义为原子变量static mutex m_mutex;
};
mutex TaskQueue::m_mutex;
atomic<TaskQueue*> TaskQueue::m_taskQ;int main()
{TaskQueue* obj = TaskQueue::getInstance();
}

使用局部静态

c++11新特性有如下规定:如果指令逻辑进入一个未被初始化的声明标量,所有并发执行应当等待该变量完成初始化。

#include<iostream>using namespace std;class TaskQueue
{
public:TaskQueue(const TaskQueue& obj) = delete;//禁用拷贝构造TaskQueue& operator = (const TaskQueue& obj) = delete;//禁用赋值构造static TaskQueue* getInstance()  //获取单例的方法{static TaskQueue m_taskQ; //未被初始化return &m_taskQ;}void print(){cout << "hello, world!!!" << endl;}private:TaskQueue() = default; //无参构造
};int main()
{TaskQueue* obj = TaskQueue::getInstance();obj->print();
}

饿汉模式和懒汉模式的区别

懒汉模式的缺点是在创建实例对象的时候有安全问题,但这样可以减少内存的浪费(如果用不到就不去申请内存了)。饿汉模式则相反,在我们不需要这个实例对象的时候,它已经被创建出来,占用了一块内存。对于现在的计算机而言,内存容量都是足够大的,这个缺陷可以被无视。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/96722.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

12P2532X162-233A KJ3222X1-BA1 CE4003S2B3 EMERSON CONTROLLER

12P2532X162-233A KJ3222X1-BA1 CE4003S2B3 EMERSON CONTROLLER EDGEBoost I/O模块是一种可扩展的模块化解决方案&#xff0c;集成到Premio的工业计算机中&#xff0c;通过即插即用的可扩展性提供增强的可靠性。这些附加模块有助于解决在加固边缘出现的设计限制和兼容性问题。…

JDBC-day01(JDBC概述,获取数据库连接)

一&#xff1a;JDBC概述 1.数据持久化 持久化(persistence)&#xff1a;把数据保存到可掉电式存储设备中以供之后使用。大多数情况下&#xff0c;数据持久化意味着将内存中的数据保存到硬盘上加以”固化”&#xff0c;而持久化的实现过程大多通过各种关系数据库来完成。简单来…

gorm 自定义时间、字符串数组类型

文章目录 自定义时间类型自定义字符串数组测试与完整代码测试代码测试结果 GORM 是GO语言中一款强大友好的ORM框架&#xff0c;但在使用过程中内置的数据类型不能满足以下两个需求&#xff0c;如下&#xff1a; time.Time类型返回的是 2023-10-03T09:12:08.5352808:00这种字符串…

美国各流域边界下载,并利用arcgis提取与处理

一、边界数据的下载 一般使用最普遍的流域边界数据是从HydroSHEDS官网下载: HydroBASINS代表一系列矢量多边形图层&#xff0c;以全球尺度呈现次级流域边界。该产品的目标是提供一种无缝的全球覆盖&#xff0c;其中包含了不同尺度&#xff08;从数十到数百万平方千米&#xf…

Zygote Secondary:加速应用启动的未来之路

Zygote Secondary&#xff1a;加速应用启动的未来之路 1. 引言 在现代的移动应用开发中&#xff0c;启动速度和响应性能是用户体验的重要方面。然而&#xff0c;传统的 Android 进程管理方式在启动应用时会出现性能瓶颈&#xff0c;导致启动时间过长和资源占用过多。为了解决…

Linux基本指令(中)——“Linux”

各位CSDN的uu们好呀&#xff0c;今天&#xff0c;小雅兰的内容是Linux基本指令呀&#xff01;&#xff01;&#xff01;下面&#xff0c;让我们进入Linux的世界吧&#xff01;&#xff01;&#xff01; cp指令&#xff08;重要&#xff09; mv指令&#xff08;重要&#xff09…

“益路同行”栏目专访 第06期—小星星关爱联盟创始人魏洁荣老师

中国善网在本届&#xff08;第十届&#xff09;慈展会上特别推出了《益路同行》采访栏目&#xff0c;《益路同行》栏目旨在寻觅公益之路上同行者的故事&#xff0c;挖掘公益更深层次的内涵&#xff0c;探索新时代公益发展道路。希望公益企业、人物、故事被更多人看到&#xff0…

外卖小程序源码vs定制开发:何时选择哪种方式?

在数字餐饮行业的蓬勃发展中&#xff0c;外卖应用程序已经成为餐厅和创业者的必备工具。然而&#xff0c;当涉及到开发外卖应用程序时&#xff0c;您会面临一个重要的决策&#xff1a;是使用外卖小程序源码还是进行定制开发&#xff1f;这两种方法各有优势和劣势&#xff0c;取…

【刷题笔记10.5】LeetCode:排序链表

LeetCode&#xff1a;排序链表 一、题目描述 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 二、分析 这题咱们默认要求&#xff1a;空间复杂度为O(1)。所以这把咱们用自底向上的方法实现归并排序&#xff0c;则可以达到O(1) 的空间复杂…

【itext7】使用itext7将多个PDF文件、图片合并成一个PDF文件,图片旋转、图片缩放

这篇文章&#xff0c;主要介绍使用itext7将多个PDF文件、图片合并成一个PDF文件&#xff0c;图片旋转、图片缩放。 目录 一、itext7合并PDF 1.1、引入依赖 1.2、合并PDF介绍 1.3、采用字节数组方式读取PDF文件 1.4、合并多个PDF文件 1.5、合并图片到PDF文件 1.6、旋转图…

王杰C++day1

#include <iostream>using namespace std;int main() {cout << "输入一个字符串&#xff1a;" << endl;string str;int a 0,b 0,c 0,d 0,e 0;getline(cin,str);for(int i 0;i < (int)str.size();i){if(str[i] > A && str[i] &…

接口自动化测试介入项目管理流程

上图为接口自动化测试介入梧桐项目管理流程图 前景和目标&#xff1a; 现在公司的项目流程都是全部开发完成后提交到测试环境进行测试&#xff0c;导致测试人员在开发编码过程中相对清闲&#xff0c;除了完成测试用例之外没有其他事情可做&#xff0c;而当进入测试阶段又会变…

兽药经营小程序微信商城的作用是什么

无论家宠还是畜牧养殖&#xff0c;生病杀虫总是不可少的&#xff0c;尤其对铲屎官们来说&#xff0c;宠物的健康状况很重要&#xff0c;以此花费百元千元也并不觉心疼&#xff0c;兽药的需求度也是非常高&#xff0c;那么对相关从业商家来说&#xff0c;遇到的难题有哪些&#…

Sql server 使用DBCC Shrinkfile 收缩日志文件

磁盘空间有限&#xff0c;需要收缩日志文件释放空间。 数据库名称上右击属性->文件,逻辑名称日志文件默认名称为“_log”结尾。 alter database 数据库 set recovery simple dbcc shrinkfile(XXX_log,2,truncateonly) alter database 数据库 set recovery full

安全基础 --- MySQL数据库的《锁》解析

MySQL的ACID &#xff08;1&#xff09;ACID是衡量事务的四个特性 原子性&#xff08;Atomicity&#xff0c;或称不可分割性&#xff09;一致性&#xff08;Consistency&#xff09;隔离性&#xff08;Isolation&#xff09;持久性&#xff08;Durability&#xff09; &…

数据结构与算法(七)--使用链表实现栈

一、前言 之前我们已经学习了链表的所有操作及其时间复杂度分析&#xff0c;我们可以了解到对于链表头的相关操作基本都是O(1)的&#xff0c;例如链表头增加、删除元素&#xff0c;查询元素等等。那我们其实有一个数据结构其实可以完美利用到这些操作的特点&#xff0c;都是在…

Python与数据分析--Pandas操作进阶

目录 1.文件读取方式 1.1.绝对路径读取文件 1.2.相对路径读取文件 2.列表数据操作 2.1.列索引指定 2.2.代码数据对齐 3.创建新CSV文件 4.缺失值处理 4.1.缺失值创建 4.2.缺失值检索 4.3.缺失值查询 4.3.1.isnull()函数判断 4.3.2.notnull()函数判断 4.3.3.any()函数…

在win10里顺利安装了apache2.4.41和php7.4.29以及mysql8.0.33

一、安装apache和php 最近在学习网站搭建。其中有一项内容是在windows操作系统里搭建apachephp环境。几天前根据一本书的上的说明尝试了一下&#xff0c;在win10操作系统里安装这两个软件&#xff1a;apache2.4.41和php7.4.29&#xff0c;安装以后apche能正常启动&#xff0c;…

OpenCV实现视频的追踪(meanshift、Camshift)

目录 1&#xff0c;meanshift 1.1 算法流程 1.2 算法实现 1.3 代码实现 1.4 结果展示 1&#xff0c;meanshift 1.1 算法流程 1.2 算法实现 1.3 代码实现 import numpy as np import cv2 as cv# 读取视频 cap cv.VideoCapture(video.mp4)# 检查视频是否成功打开 if n…

(c语言)经典bug

#include<stdio.h> //经典bug int main() { int i 0; int arr[10] {1,2,3,4,5,6,7,8,9,10}; for (i 0; i < 12; i) //越界访问 { arr[i] 0; printf("hehe\n"); } return 0; } 注&#xff1a;输出结果为死循…