网络编程(13)——单例模式
十三、day13
今天学习如何单例模式实现逻辑层的设计。内容包括服务器如何能捕获信号使其安全退出、单例模标类
1. 什么是单例模式?
单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点,单例模式是在内存中仅会创建一次对象的设计模式。
//通过静态成员变量实现单例
//懒汉式
class Single2
{
private:Single2() {}Single2(const Single2 &) = delete;Single2& operator=(const Single2&) = delete;
public:static Single2& GetInst(){static Single2 single;return single;}
};
-
在上面的代码块中,定义了一个Single2类,Single2类的默认构造函数被声明为私有,且删除拷贝构造函数和赋值运算符,确保Single2类不能通过拷贝创建或赋值创建新的实例。
-
Single2类只有一个公共静态方法GetInst(),用于获取Single2类的唯一实例。
-
局部静态成员single用于存储Single2类的唯一实例,通过返回single即可返回该实例。
单例模式的简单实现可总结为:
-
构造方法是私有的
-
对外暴露的获取访问是公有的静态的
-
唯一实例的存储方式是静态的
风险:上述代码块(懒汉式)生成了唯一实例,但在多线程方式下生成的实例可能会存在多个(如果多个线程同时调用GetInst()时都会去实例化一个simgle对象,使得Single2类被重复实例化)
1)单例模式的分类
-
饿汉式:类加载就会导致该单实例对象被创建
-
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时被创建
懒汉式创建对象的方法是函数中创建静态局部变量,这样只有在对象第一次被使用时才会创建实例;而饿汉式一般已经在类中提前声明了静态变量single,这样在类加载时便已经提前创建好实例。
上述代码块的单例模式就是通过懒汉式实现的,静态变量single在第一次使用Single2类的GetInst()时被创建,其声明周期随着进程结束而结束。
饿汉式单例模式实现
//饿汉式
class Single2Hungry
{
private:Single2Hungry() { }Single2Hungry(const Single2Hungry&) = delete;Single2Hungry& operator=(const Single2Hungry&) = delete;
public:static Single2Hungry* GetInst(){if (single == nullptr)single = new Single2Hungry();return single;}
private:static Single2Hungry* single;
};
饿汉模式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可,可以避免线程安全问题。
多线程和单线程下进行测试
//饿汉式初始化
Single2Hungry* Single2Hungry::single = Single2Hungry::GetInst();
void thread_func_s2(int i)
{cout << "this is thread " << i << endl;cout << "inst is " << Single2Hungry::GetInst() << endl;
}
void test_single2hungry()
{cout << "s1 addr is " << Single2Hungry::GetInst() << endl;cout << "s2 addr is " << Single2Hungry::GetInst() << endl;for (int i = 0; i < 3; i++){thread tid(thread_func_s2, i);tid.join();}
}
int main(){test_single2hungry()
}
输出为
s1 addr is 0x1e4b00
s2 addr is 0x1e4b00
this is thread 0
inst is 0x1e4b00
this is thread 1
inst is 0x1e4b00
this is thread 2
inst is 0x1e4b00
可见无论单线程还是多线程模式下,通过静态成员变量的指针实现的单例类都是唯一的。饿汉式是在程序启动时就进行单例的初始化,这种方式也可以通过懒汉式调用,无论饿汉式还是懒汉式都存在一个问题,就是什么时候释放内存?多线程情况下,释放内存就很难了,还有二次释放内存的风险。
2)懒汉式的改进
上面提到了懒汉式有一定的风险:在多线程下可能会创建多个Single2的实例,如果多个线程同时调用GetInst()时都会去实例化一个simgle对象,使得Single2类被重复实例化)。
通过对GetInst()方法枷锁或者对Single2类进行加锁,可以解决该风险,每个线程在进入方法前,都要等到别的线程都离开此方法,不会有两个线程同时进入此方法。
//懒汉式指针
//即使创建指针类型也存在问题
class SinglePointer
{
private:SinglePointer() { }SinglePointer(const SinglePointer&) = delete;SinglePointer& operator=(const SinglePointer&) = delete;
public:static SinglePointer *GetInst(){if (single != nullptr){return single;}s_mutex.lock();if (single != nullptr){s_mutex.unlock();return single;}single = new SinglePointer();s_mutex.unlock();return single;}
private:static SinglePointer *single;static mutex s_mutex;
};
该段代码块通过双重检验枷锁进行加锁,避免了直接加锁造成的问题:每次去获取对象都需要先获取锁,并发性能非常地差。
双重检验枷锁:
-
如果已经实例化了,则不需要加锁,直接返回实例化对象
-
如果没有实例化对象则加锁,然后再判断一次有没有实例化
-
如果实例化了就解锁并返回实例化对象
-
如果没有实例化就初始化实例化对象,并解锁返回实例化对象
进行测试
//懒汉式
//在类的cpp文件定义static变量
SinglePointer *SinglePointer::single = nullptr;
std::mutex SinglePointer::s_mutex;void thread_func_lazy(int i)
{cout << "this is lazy thread " << i << endl;cout << "inst is " << SinglePointer::GetInst() << endl;
}
void test_singlelazy()
{for (int i = 0; i < 3; i++){thread tid(thread_func_lazy, i);tid.join();}//何时释放new的对象?造成内存泄漏
}
int main(){test_singlelazy();
}
输出为
this is lazy thread 0
inst is 0xbc1700
this is lazy thread 1
inst is 0xbc1700
this is lazy thread 2
inst is 0xbc1700
尽管多线程下懒汉式可能会创建多个Single2类实例的问题被解决,但无论懒汉式还是饿汉式,都有一个共同的问题需要解决:什么时候释放内存?多线程下多次delete也会造成崩溃。
3)智能指针方法
使用智能指针方法自动回收内存的机制设计单例类
//利用智能指针解决释放问题
class SingleAuto
{
private:SingleAuto() { }SingleAuto(const SingleAuto&) = delete;SingleAuto& operator=(const SingleAuto&) = delete;
public:~SingleAuto(){cout << "single auto delete success " << endl;}static std::shared_ptr<SingleAuto> GetInst() {if (single != nullptr) {return single;}s_mutex.lock();if (single != nullptr) {s_mutex.unlock();return single;}single = std::make_shared<SingleAuto>();s_mutex.unlock();return single;}
private:static std::shared_ptr<SingleAuto> single;static mutex s_mutex;
};
SingleAuto类的GetInst()返回std::shared_ptr<SingleAuto>类型的变量single。因为single是静态成员变量,所以会在进程结束时被回收。智能指针被回收时会调用内置指针类型的析构函数,从而完成内存的回收。
测试
// 智能指针方式
std::shared_ptr<SingleAuto> SingleAuto::single = nullptr;
mutex SingleAuto::s_mutex;
void test_singleauto()
{auto sp1 = SingleAuto::GetInst();auto sp2 = SingleAuto::GetInst();cout << "sp1 is " << sp1 << endl;cout << "sp2 is " << sp2 << endl;//此时存在隐患,可以手动删除裸指针,造成崩溃// delete sp1.~SingleAuto();
}
int main(){test_singleauto();
}
输出:
sp1 is 0x1174f30
sp2 is 0x1174f30
智能指针方式不存在内存泄漏,但是有一个隐患:单例类的析构函数是公有成员,如果被人手动调用会存在崩溃问题,比如将上边测试中的注释打开,程序会崩溃
4)辅助类智能指针单例模式
将析构函数私有化,在构造智能指针时指定删除器,通过传递一个辅助类或者辅助函数帮助智能指针回收内存时调用指定的析构函数。因为析构函数私有化以后,智能指针在引用计数归零后无法调用对象的析构函数进行销毁。所以必须指定一个删除器,该删除器是单例类的友元类,可以访问单例类的私有或公有成员,可以通过删除器间接调用单例类的析构函数。
// safe deletor
//该类定义仿函数调用SingleAutoSafe析构函数
class SingleAutoSafe;class SafeDeletor
{
public:void operator()(SingleAutoSafe *sf){cout << "this is safe deleter operator()" << endl;delete sf;}
};class SingleAutoSafe
{
private:SingleAutoSafe() {}~SingleAutoSafe(){cout << "this is single auto safe deletor" << endl;}SingleAutoSafe(const SingleAutoSafe &) = delete;SingleAutoSafe &operator=(const SingleAutoSafe &) = delete;//定义友元类,通过友元类调用该类析构函数friend class SafeDeletor;
public:static std::shared_ptr<SingleAutoSafe> GetInst(){if (single != nullptr){return single;}s_mutex.lock();if (single != nullptr){s_mutex.unlock();return single;}//额外指定删除器single = std::shared_ptr<SingleAutoSafe>(new SingleAutoSafe, SafeDeletor());//也可以指定删除函数// single = std::shared_ptr<SingleAutoSafe>(new SingleAutoSafe, SafeDelFunc);s_mutex.unlock();return single;}
private:static std::shared_ptr<SingleAutoSafe> single;static mutex s_mutex;
};
如果是提前声明SafeDeletor ,而先定义SingleAutoSafe,会造成 incomplete type(类型不完整)的错误,因为被提前声明的类,可以在后面定义,但在声明类前面定义的其他类中使用声明类时,只能使用声明类的指针,而不能创建声明类的实例。
SafeDeletor类中重载了(),实现类模拟函数的作用。
SafeDeletor要写在SingleAutoSafe上边,并且SafeDeletor要声明为SingleAutoSafe类的友元类,这样就可以访问SingleAutoSafe的析构函数了。在构造single时制定了SafeDeletor(),single在回收时,会调用仿函数SafeDeletor(),从而完成内存的销毁。同时,SingleAutoSafe的析构函数为私有,无法被外界显式调用。
5)通用的单例模板类
通过声明单例的模板类,然后继承这个单例模板类的所有类就是单例类了,可以达到泛型编程提高效率的目的
template <typename T>
class Single_T
{
protected:Single_T() = default;Single_T(const Single_T<T> &st) = delete;Single_T &operator=(const Single_T<T> &st) = delete;~Single_T(){cout << "this is auto safe template destruct" << endl;}
public:static std::shared_ptr<T> GetInst(){if (single != nullptr){return single;}s_mutex.lock();if (single != nullptr){s_mutex.unlock();return single;}//额外指定删除器single = std::shared_ptr<T>(new T, SafeDeletor_T<T>());//也可以指定删除函数// single = std::shared_ptr<SingleAutoSafe>(new SingleAutoSafe, SafeDelFunc);s_mutex.unlock();return single;}
private:static std::shared_ptr<T> single;static mutex s_mutex;
};//模板类的static成员要放在h文件里初始化
template <typename T>
std::shared_ptr<T> Single_T<T>::single = nullptr;
template <typename T>
mutex Single_T<T>::s_mutex;
模板类的静态成员变量要在头文件中初始化,而非模板类的静态成员变量一般在cpp文件中初始化。
应用:定义一个网络的单例类,继承上述模板类,并将构造和析构设置为私有,同时设置友元保证自己的析构和构造可以被友元类调用.
class SingleNet : public Single_T<SingleNet>
{
private:SingleNet() = default;SingleNet(const SingleNet &) = delete;SingleNet &operator=(const SingleNet &) = delete;~SingleNet() = default;friend class SafeDeletor_T<SingleNet>;friend class Single_T<SingleNet>;
};
删除器SafeDeletor_T和单例模板类Single_T都是模板,需要提前定义。
测试
void test_singlenet()
{auto sp1 = SingleNet::GetInst();auto sp2 = SingleNet::GetInst();cout << "sp1 is " << sp1 << endl;cout << "sp2 is " << sp2 << endl;
}
5)总结
1. 为什么要有单例模式?
使用单例模式的原因
-
资源控制:单例模式可以用来控制系统中的资源,例如数据库连接池或线程池,确保这些关键资源不会被过度使用。
-
内存节省:当需要一个对象进行全局访问,但创建多个实例会造成资源浪费时,单例模式可以确保只创建一个实例,节省内存。
-
共享:单例模式允许状态或配置信息在系统的不同部分之间共享,而不需要传递实例。
-
延迟初始化:单例模式支持延迟初始化,即实例在首次使用时才创建,而不是在类加载时。
-
一致的接口:单例模式为客户端提供了一个统一的接口来获取类的实例,使得客户端代码更简洁。
-
易于维护:单例模式使得代码更易于维护,因为所有的实例都使用相同的实例,便于跟踪和修改变更。
单例模式的应用场景
-
配置管理器:在应用程序中,配置信息通常只需要读取一次,并全局使用。单例模式用于确保配置管理器只被实例化一次。
-
日志记录器:一个系统中通常只需要一个日志记录器来记录所有的日志信息,使用单例模式可以避免日志文件的重复写入。
-
数据库连接池:数据库连接是一种有限的资源,使用单例模式可以确保数据库连接池的唯一性,并且能够重用连接,减少连接创建和销毁的开销。
-
线程池:类似于数据库连接池,线程池也是有限的资源,使用单例模式可以避免创建过多的线程,提高应用程序的并发性能。
-
任务调度器:在需要全局调度和管理的场景下,如定时任务调度器,单例模式提供了一个集中的管理方式。
-
网站的计数器:一般也是采用单例模式实现,否则难以同步。
2. 单例模式中GetInst()为什么是静态的?
1)静态方法可以通过类名直接访问,无需创建类的实例。这样可以方便地获取唯一实例,而不需要先实例化类。 2)类的构造函数已经被私有化,无法直接实例化对象,智能通过定义静态成员函数的方式通过类名::方法名的方式进行构造访问 3)静态局部变量的方式是线程安全的,能确保在多线程环境下也只会创建一个实例
相关文章:
网络编程(13)——单例模式
十三、day13 今天学习如何单例模式实现逻辑层的设计。内容包括服务器如何能捕获信号使其安全退出、单例模标类 1. 什么是单例模式? 单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点&…...
基于定制开发与2+1链动模式的商城小程序搭建策略
摘要:本文探讨商城小程序的搭建策略,对比自主组建团队和第三方开发两种方式,强调以第三方开发模式为主的优势。阐述在第三方开发模式下,结合定制开发和21链动模式,如何搭建一款有助于企业商业模式创新与智能商业升级的…...
银河麒麟,apt 安装软件报错640Unknown Status
今天把银行麒麟的机器恢复出厂了,然后apt install 安装极其不稳定,故障现象如下图所示: 错误提示里面有: 640 Unknown Status [IP: 106.116.184.122 80] E: 无法下载 http://archive.kylinos.cn/kylin/KYLIN-ALL/pool/universe/f…...
python UNIT 3 选择与循环(2)
目录 1。循环的优化 经典优化分析: 未优化的代码: 细节分析: 优化后的代码: 优化的细节: 性能对比 优化的关键在于: 经典习题讲解:(紫色的解析请重点关注一下) 1。例三 个人代码解析…...
828华为云征文|部署在线文档应用程序 CodeX Docs
828华为云征文|部署在线文档应用程序 CodeX Docs 一、Flexus云服务器X实例介绍二、Flexus云服务器X实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置2.4 Docker 环境搭建 三、Flexus云服务器X实例部署 CodeX Docs3.1 CodeX Docs 介绍3.2 CodeX Docs 部署3.3 CodeX…...
Linux的多线程(线程的创建,退出,取消请求,取消处理例程,线程属性的设置)
进程:是系统分配资源的最小单位,系统会为每一个进程分配一块独立的虚拟内存空间 线程:是系统调度的最小单位,系统不会为线程分配新的内存空间,但是线程也参与系统调度 cpu把时间片分给每一个进程,进程中的时间片再切分分给每一个线程,所以线程也会得到…...
git 本地代码关联远程仓库并推送
初始化代码仓库 如果你的本地项目还没有使用Git管理,首先需要在项目根目录下初始化一个Git仓库 git init添加远程仓库地址 使用 git remote add 命令添加远程仓库 git remote add origin https://github.com/username/repository.git获取远程分支信息 使用 git…...
推荐一个可以把PDF样本册转换为翻页电子书的网站
随着互联网的普及,越来越多的企业和个人开始意识到线上展览的重要性。如何将实体样本册转化为线上版本,让更多人了解和欣赏自己的产品与服务? 一、网站简介 这款PDF样本册免费上传网站名为“FLBOOK”,致力于为广大用户提供便捷…...
【Linux 23】线程池
文章目录 🌈 一、线程池的概念🌈 二、线程池的应用场景🌈 三、线程池的实现 🌈 一、线程池的概念 线程池 (thread pool) 是一种利用池化技术的线程使用模式。 虽然创建线程的代价比创建进程的要小很多,但小并不意味着…...
Rust SQLite 跨平台使用
引言 Rust因其内存安全性和高性能受到越来越多开发者的青睐。在许多项目中,SQLite作为一种轻量级的嵌入式数据库,与Rust的结合为跨平台应用程序提供了强大的支持。本文将详细探讨Rust如何实现跨平台功能,如何在不同平台上使用Rust库…...
docker运行arm64架构的镜像、不同平台镜像构建
背景 Docker 允许开发者将应用及其依赖打包成一个轻量级、可移植的容器,实现“一次构建,到处运行”的目标。然而,不同的操作系统和硬件架构对容器镜像有不同的要求。例如,Linux 和 Windows 系统有不同的文件系统和系统调用&#…...
vue基于Spring Boot框架的高校实验室预约管理系统
目录 毕设制作流程功能和技术介绍系统实现截图开发核心技术介绍:使用说明开发步骤编译运行代码执行流程核心代码部分展示可行性分析软件测试详细视频演示源码获取 毕设制作流程 (1)与指导老师确定系统主要功能; (2&am…...
Linux中find命令详解
记录linux中find命令的详细用法。 文章目录 find命令简介基本语法常用选项-name-iname-type-size-mtime,-atime,-ctime-perm-user-group-delete-exec-printand or find --help find命令简介 find 是一个搜索目录树以查找一个文件或一组文件的程序。它遍历目录树并报告与用户规…...
无水印短视频素材下载网站有哪些?十个高清无水印视频素材网站分享
你知道怎么下载无水印视频素材吗?今天小编就给大家推荐十个高清无水印视频素材下载的网站,如果你也是苦于下载高清无水印的短视频素材,赶紧来看看吧~ 1. 稻虎网 首推的是稻虎网。这个网站简直就是短视频创作者的宝库。无论你需要…...
SpringBoot+Activiti7工作流入门实例
目录 文章目录 目录准备Activiti建模工具1、BPMN-js在线设计器1.1 安装1.2 使用说明1.3运行截图2、IDEA安装Activiti Designer插件2.1安装插件2.2 设置编码格式防止中文乱码2.3 截图简单工作流入门实例1. 新建Spring Boot工程2. 引入Activiti相关依赖添加版本属性指定仓库添加依…...
Azure OpenAI检索增强微调:使用 GPT-4o 对 GPT-4o mini 进行微调,以适应特定领域的应用
定制是关键! 生成式人工智能对企业最有影响力的应用之一是创建自然语言界面,这些界面经过定制,可以使用特定领域和用例数据来提供更好、更准确的响应。这意味着回答有关特定领域的问题,例如银行、法律和医疗领域。 我们经常谈…...
ISP Pipeline
系列文章目录 文章目录 系列文章目录前言一、RAW域二、RGB域三、YUV域总结 前言 一、RAW域 黑电平校正(BLC)数字增益调整(DGain)自动白平衡(AWB)局部色调映射(LTM)坏点修复…...
< IDE编程环境配置>
IDE编程环境配置 LIB,DLL区别 我们在写项目时会链接(调用)第3方库,或者比如在vs的解决方案solution创建项目project时,不仅可以开发可执行程序exe(可单独运行)(windows/控制台 应用…...
Golang | Leetcode Golang题解之第448题找到所有数组中消失的数字
题目: 题解: func findDisappearedNumbers(nums []int) (ans []int) {n : len(nums)for _, v : range nums {v (v - 1) % nnums[v] n}for i, v : range nums {if v < n {ans append(ans, i1)}}return }...
【Spring Boot 入门三】Spring Boot与数据库集成 - 构建数据驱动的应用
一、引言 在之前的文章中,我们已经对Spring Boot有了初步的认识,了解了如何构建第一个Spring Boot应用,以及如何通过配置文件来掌控应用的设置。这些知识为我们进一步探索Spring Boot与数据库的集成奠定了坚实的基础。 数据库是现代应用的核…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
