C++设计模式:单例模式(十)
1、单例设计模式
-
单例设计模式,使用的频率比较高,整个项目中某个特殊的类对象只能创建一个
-
并且该类只对外暴露一个public方法用来获得这个对象。
-
单例设计模式又分懒汉式和饿汉式,同时对于懒汉式在多线程并发的情况下存在线程安全问题
-
饿汉式:类加载的准备阶段就会将static变量、代码块进行实例化,最后只暴露一个public方法获得实例对象。
-
懒汉式:当需要用到的时候再去加载这个对象。这时多线程的情况下可能存在线程安全问题
-
-
对于饿汉式这里不做具体的解释,本节只讨论多线程与懒汉式的线程安全问题
2、单线程下的懒汉模式
2.1、单例对象的创建:
-
将类指针对象进行静态私有化,并且在类外初始化这个对象为空;静态能保证的是这个对象属于这个类不属于任何一个对象!
-
私有化空构造器防止可以实例化对象
-
对外暴露一个public方法获取该对象,如果在获取时发现该对象为空,那么进行实例化,否则直接返回
-
因此可以看到实例化只有一次,多次获取到的对象的地址属于同一个
-
可以通过内部类的方式进行析构
- 首先在单例类内部进行私有化一个内部类
- 对外暴露的public获取instance的对象接口在new实例化对象的时候创建一个内部类静态成员
- 内部类静态成员的好处是只有一份
- 当作用域结束时内部类就会负责析构掉主类的静态成员对象
class Single_Instance {
private:static Single_Instance *instance;Single_Instance() {}Single_Instance(const Single_Instance & s){}class inner_class {public:~inner_class(){if(Single_Instance::instance){delete Single_Instance::instance;Single_Instance::instance = NULL;std::cout << "inner_class::~inner_class(), 析构Single_Instance::instance对象" << std::endl;}}};
public:static Single_Instance *get_Instance(){if(instance == NULL){instance = new Single_Instance();static inner_class innerClass;}return instance;}void func(){std::cout << "func(), &instance = " << instance << std::endl;}
};
Single_Instance *Single_Instance::instance = NULL;
3、单例模式与多线程
-
单例模式的对象可能会被多个线程使用,但是又必须保证这个单例的对象只有一份
-
不能重复创建、也必须保证这个对象在多线程使用过程中不会因为创建而产生数据安全问题,即多线程抢占的创建这一个对象
class Single_Instance {
private:static Single_Instance *instance;Single_Instance() {}Single_Instance(const Single_Instance & s){}class inner_class {public:~inner_class(){if(Single_Instance::instance){delete Single_Instance::instance;Single_Instance::instance = NULL;std::cout << "inner_class::~inner_class(), 析构Single_Instance::instance对象" << std::endl;}}};
public:static Single_Instance *get_Instance(){if(instance == NULL){instance = new Single_Instance();static inner_class innerClass;}return instance;}void func(){std::cout << "func(), &instance = " << instance << std::endl;}
};
Single_Instance *Single_Instance::instance = NULL;void thread_func()
{std::cout << "子线程开始执行了" << std::endl;Single_Instance *instance = Single_Instance::get_Instance();std::cout << "thread_func, &instance = " << instance << std::endl;std::cout << "子线程执行结束了" << std::endl;
}void test2()
{std::thread mythread1(thread_func);std::thread mythread2(thread_func);std::thread mythread3(thread_func);std::thread mythread4(thread_func);mythread1.join();mythread2.join();mythread3.join();mythread4.join();
}

可以看到实例化不止一个单例对象,这一现象违反了单例的思想,因此需要在多线程抢占创建时进行互斥(mutex)
3.1、解决方案(一)
- 使用互斥量的方式,对线程访问获取对象进行阻塞
- 但是不难发现问题,其实这个对象只创建一次,之后的访问单纯的获取这个对象也要进行加锁逐个排队访问临界区,这一现象导致效率极低
std::mutex mutex_lock;
static Single_Instance *get_Instance(){std::unique_lock<std::mutex> uniqueLock(mutex_lock);if(instance == NULL){instance = new Single_Instance();static inner_class innerClass;}return instance;
}
3.2、解决方式(二)
双重检查机制(DCL)进行绝对安全解决
- 双重检查:
- 首先在锁外面加入一个if判断,判断这个对象是否存在,如果存在就没有必要上锁创建,直接返回即可
- 如果对象不存在,首选进行加锁,然后在if判断对象是否存在,这个if的意义在于当多个线程阻塞在mutex锁头上时
- 突然有一个线程1创建好了,那么阻塞在mutex锁头上的线程2、3、4…都不用再继续创建,因此在加一个if判断
这里还需要解释一下volatile关键字:
-
volatile关键字的作用是防止cpu指令重排序,重排序的意思就是干一件事123的顺序,cpu可能重排序为132
-
为什么需要防止指令重排序,因为对象的new过程分为三部曲:
(1)分配内存空间、(2)执行构造方法初始化对象、(3)将这个对象指向这个空间;
由于程序运行CPU会进行指令的重排序,如果执行的指令是132顺序,A线程执行完13之后并没有完成对象的初始化、而这时候转到B线程;B线程认为对象已经实例化完毕、其实对象并没有完成初始化!产生错误
static Single_Instance *get_Instance(){if(instance == NULL){std::unique_lock<std::mutex> uniqueLock(mutex_lock);if(instance == NULL){instance = new Single_Instance();static inner_class innerClass;}}return instance;
}
3.3、解决方案(三)
- 但volatile关键字并不跨平台,而在C++11中提供了一种新的标准来解决这一问题,并且跨平台
- 可以通过atomic原子类来保证
- 将对象声明为原子类的指针
std::atomic_thread_fence(std::memory_order_acquire):获取内存屏蔽的屏障,关闭reorderstd::atomic_thread_fence(std::memory_order_release):将instance对象创建完毕之后进行解开内存屏障instance.store(tmp, std::memory_order_relaxed):将对象store到内存中。
- Atomic类主要通过CAS锁来实现的,具体点击这里
class Single_Instance {
private:std::atomic<Single_Instance*> instance;Single_Instance() {}Single_Instance(const Single_Instance & s){}
public:Single_Instance *get_Instance(){Single_Instance *tmp = instance.load(std::memory_order_relaxed);std::atomic_thread_fence(std::memory_order_acquire);if(tmp == NULL){std::unique_lock<std::mutex> uniqueLock(mutex_lock);tmp = instance.load(std::memory_order_relaxed);if(tmp == NULL){tmp = new Single_Instance();std::atomic_thread_fence(std::memory_order_release);instance.store(tmp, std::memory_order_relaxed);}}return tmp;}
};
相关文章:
C++设计模式:单例模式(十)
1、单例设计模式 单例设计模式,使用的频率比较高,整个项目中某个特殊的类对象只能创建一个 并且该类只对外暴露一个public方法用来获得这个对象。 单例设计模式又分懒汉式和饿汉式,同时对于懒汉式在多线程并发的情况下存在线程安全问题 饿汉…...
openssl3.2 - exp - zlib
文章目录 openssl3.2 - exp - zlib概述笔记命令行实现程序实现备注 - 压缩时无法base64压缩时无法带口令压缩实现 - 对buffer进行压缩和解压缩测试效果工程实现main.cppCOsslZlibBuffer.hCOsslZlibBuffer.cpp总结END openssl3.2 - exp - zlib 概述 客户端和服务端进行数据交换…...
【故事】无人机学习之旅
今天是清明假期最后一天,晚上在看无人机的东西,翻到了欣飞鸽的知乎主页,读了他的一些文章。虽不曾相识,但感觉我们有很多相似的经历,也想记录一下自己的无人机学习之旅。 青铜:从使用开源飞控开始 我在大…...
torch.mean()的使用方法
对一个三维数组的每一维度进行操作 1,dim0 a torch.Tensor([0, 1, 2, 3, 4, 5,6,7]).view(2, 2, 2) print(a) mean torch.mean(a, 0) print(mean, mean.shape) 输出结果: tensor([[[0., 1.], [2., 3.]], [[4., 5.], [6., 7.]]]) tensor([[2., …...
windows安装Redis,Mongo,ES并快速基本掌握开发流程
前言 这里只是一些安装后的基础操作,后期会学习更加深入的操作 基础操作 前言RedisRedis启动idea集成Redisjedis技术 Mongodbwindows版Mongodb的安装idea整合Mongodb ES(Elasticsearch)ESwindows下载ES文档操作idea整合ES低级别ES整合高级别ES整合 Redis Redis是…...
ruoyi-nbcio-plus基于vue3的flowable的自定义业务提交申请组件的升级修改
更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio 演示地址:RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码:…...
掌握网络抓取技术:利用RobotRules库的Perl下载器一览小红书的世界
引言 在信息时代的浪潮下,人们对于获取和分析海量网络数据的需求与日俱增。网络抓取技术作为满足这一需求的关键工具,正在成为越来越多开发者的首选。而Perl语言,以其卓越的文本处理能力和灵活的特性,脱颖而出,成为了…...
典型新能源汽车热管理系统方案分析
目前行业具有代表性的热管理系统有PTC电加热方案、热泵方案(特斯拉八通阀热泵、吉利直接式热泵)、威马的柴油加热方案以及以理想为代表的插电式混动车方案。 小鹏P7整车热管理方案分析(PTC电加热方案) 小鹏P7作为小鹏汽车的第2款…...
使用Docker部署开源项目FreeGPT35来免费调用ChatGPT3.5 API
Vercel部署FreeGPT35有严重限制,玩玩就好,真用还是得docker。 限制原因: Vercel的流式响应并不是一开始写流,客户端就能立刻收到响应流,而是先写到一个缓冲区,当流关闭才一股脑的流式响应回来(不是实时流) 因此导致: …...
《Linux运维实战:Kylin V10操作系统开启安装软件保留缓存设置》
总结:整理不易,如果对你有帮助,可否点赞关注一下? 更多详细内容请参考:Linux运维实战总结 一、操作步骤 1、改系统/etc/yum.conf配置文件,开启安装软件保留缓存设置 [rootecs-90c2-0003 ~]# vim /etc/yum.…...
视频生成技术:从GAN到Latte
GANs Diffusion Model...
机器学习中的激活函数
激活函数存在的意义: 激活函数决定了某个神经元是否被激活,当这个神经元接收到的信息是有用或无用的时候,激活函数决定了对这个神经元接收到的信息是留下还是抛弃。如果不加激活函数,神经元仅仅做线性变换,那么该神经网…...
LinuxAndroid: 旋转编码器input输入事件适配(旋转输入)
rk3588s: 旋转编码器input输入事件适配 基于Android 12 kernel-5.10版本 参考文档: https://blog.csdn.net/szembed/article/details/131551950 Linux 输入设备调试详解(零基础开发)Rotary_Encoder旋转编码器驱动 通用GPIO为例 挂载input输…...
机器学习和深度学习-- 李宏毅(笔记与个人理解)Day10
Day 10 Genaral GUidance training Loss 不够的case Loss on Testing data over fitting 为什么over fitting 留到下下周哦~~ 期待 solve CNN卷积神经网络 Bias-Conplexiy Trade off cross Validation how to split? N-fold Cross Validation mismatch 这节课总体听下来比较…...
perl 交叉编译
前言 Perl是一种高级、通用、解释型、动态的编程语言。Perl设计的初衷是为了更好地处理文本处理任务,但随着时间的发展,现在它已经变成了一种强大的一般目的编程语言。Perl支持面向过程和面向对象的编程风格。 Perl的特点: 强大的字符串处…...
浅谈.版本管理工具
定义: 版本控制是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史,方便查看更改历史记录,备份以便恢复以前的版本的软件工程技术。 特点: 1、方便用于管理多人协同开发项目 2、并行开发,可实现跨区…...
【汇编语言实战】已知10个整数求最大值
C语言描述该程序流程: #include <stdio.h> int main() {int a[]{11,33,23,54,12,51,2,4,34,45};int maxa[0];for(int i1;i<9;i){if(a[i]>max){maxa[i];}}printf("%d",max); }汇编语言: include irvine32.inc .data arr dword 11…...
在 CentOS 7 上安装 Redis
在 CentOS 7 上安装 Redis 可以通过几个简单的步骤完成。以下是一种常用的方法: 更新系统: 在安装任何新软件之前,最好先更新系统的软件包列表,以确保安装的软件版本是最新的。可以使用以下命令来更新: sudo yum up…...
『51单片机』蜂鸣器
🚩 WRITE IN FRONT 🚩 🔎 介绍:"謓泽"正在路上朝着"攻城狮"方向"前进四" 🔎🏅 荣誉:2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2222年获评…...
计算机视觉 | 基于二值图像数字矩阵的距离变换算法
Hi,大家好,我是半亩花海。本实验基于 OpenCV 实现了二值图像数字矩阵的距离变换算法。首先生成一个 480x480 的黑色背景图像(定义黑色为0,白色为1),在其中随机选择了三个白色像素点作为距离变换的原点&…...
利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
HarmonyOS运动开发:如何用mpchart绘制运动配速图表
##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...
