C++多线程:单例模式与共享数据安全(七)

1、单例设计模式
  • 单例设计模式,使用的频率比较高,整个项目中某个特殊的类对象只能创建一个

  • 并且该类只对外暴露一个public方法用来获得这个对象。

  • 单例设计模式又分懒汉式和饿汉式,同时对于懒汉式在多线程并发的情况下存在线程安全问题

    • 饿汉式:类加载的准备阶段就会将static变量、代码块进行实例化,最后只暴露一个public方法获得实例对象。

    • 懒汉式:当需要用到的时候再去加载这个对象。这时多线程的情况下可能存在线程安全问题

  • 对于饿汉式这里不做具体的解释,本节只讨论多线程与懒汉式的线程安全问题

2、单线程下的懒汉模式
2.1、单例对象的创建:
  • 将类指针对象进行静态私有化,并且在类外初始化这个对象为空;静态能保证的是这个对象属于这个类不属于任何一个对象
  • 私有化空构造器防止可以实例化对象
  • 对外暴露一个public方法获取该对象,如果在获取时发现该对象为空,那么进行实例化,否则直接返回
  • 因此可以看到实例化只有一次,多次获取到的对象的地址属于同一个
class Single_Instance {
private:static Single_Instance *instance;Single_Instance() {}
public:static Single_Instance *get_Instance(){if(instance == NULL){instance = new Single_Instance();}return instance;}void func(){std::cout << "func(), &instance = " << instance << std::endl;}
};
Single_Instance *Single_Instance::instance = NULL;void test1()
{Single_Instance *instance1 = Single_Instance::get_Instance();Single_Instance *instance2 = Single_Instance::get_Instance();instance1->func();instance2->func();
}# 输出
func(), &instance = 0x5652eefede70
func(), &instance = 0x5652eefede70
2.2、单例对象的析构
  • 很明显上面的代码缺少一个析构函数,并且似乎无从下手找一个合适的时机对其进行析构,只能等待程序运行结束操作系统回收?
  • 其实可以通过内部类的方式进行析构
    • 首先在单例类内部进行私有化一个内部类
    • 对外暴露的public获取instance的对象接口在new实例化对象的时候创建一个内部类静态成员
    • 内部类静态成员的好处是只有一份
    • 当作用域结束时内部类就会负责析构掉主类的静态成员对象
class Single_Instance {
private:static Single_Instance *instance;Single_Instance() {}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 test1()
{Single_Instance *instance1 = Single_Instance::get_Instance();Single_Instance *instance2 = Single_Instance::get_Instance();instance1->func();instance2->func();
}
#输出
func(), &instance = 0x558eb768de70
func(), &instance = 0x558eb768de70
inner_class::~inner_class(), 析构Single_Instance::instance对象
3、单例模式与多线程
  • 单例模式的对象可能会被多个线程使用,但是又必须保证这个单例的对象只有一份

  • 不能重复创建、也必须保证这个对象在多线程使用过程中不会因为创建而产生数据安全问题,即多线程抢占的创建这一个对象

class Single_Instance {
private:static Single_Instance *instance;Single_Instance() {}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;
class Single_Instance {
private:static Single_Instance *instance;Single_Instance() {}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(){std::unique_lock<std::mutex> uniqueLock(mutex_lock);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();
}
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线程认为对象已经实例化完毕、其实对象并没有完成初始化!产生错误

  • 但这个问题在C++11中已经禁止了重排序,因此不需要使用volatile关键字,但在Java和一些其他语言中可能有,Java中这个关键字是针对即时编译器JIT进行指令重排序的

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;
}

只需要把上面的代码改成这个样子即可

4、std::call_once()
  • std::call_once()是C++11引入的函数,该函数的功能就是保证一个方法只会被调用一次。

    • 参数二:一个函数名func

    • 参数一:std::once_flag一个标记,本质是一个结构体。该标志可以用于标记参数二该函数是否已经调用过了

    • 参数三:参数二函数的参数

  • std::call_once()具有互斥量的这种能力,且效率上比mutex互斥量效率更高,因此也可以使用这个函数对单例的线程安全进行保证

    • 当call_once调用过一次之后,std::once_flag将会被修改标记(已调用),那么之后都不会在调用
  • 下面看个代码举例,可以看到create_Instance()函数中对于这个函数只执行了一次,完全ojbk。

class Single_Instance {
private:static Single_Instance *instance;static std::once_flag instance_flag;Single_Instance() {}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 void create_Instance(){instance = new Single_Instance();static inner_class innerClass;}static Single_Instance *get_Instance(){std::call_once(instance_flag, create_Instance);return instance;}void func(){std::cout << "func(), &instance = " << instance << std::endl;}
};
Single_Instance *Single_Instance::instance = NULL;
std::once_flag Single_Instance::instance_flag;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 test3()
{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();
}

在这里插入图片描述

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

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

相关文章

深入理解计算机系统 家庭作业 2.75

/* 书中的公式是w位的公式(mod 就是为了截断成w位),我们现在做的是2w位中的前w位 注意书上这句话:由于模运算符,所有带有权重和的项都丢掉 对应到本题 该项除以后还是超过了2w位所以被丢弃了,因为题目说了只有2w位 这个式子除以就是我们想要的最终结果 函数signed_high_p…

前端学习<四>JavaScript基础——03-常量和变量

常量&#xff08;字面量&#xff09;&#xff1a;数字和字符串 常量也称之为“字面量”&#xff0c;是固定值&#xff0c;不可改变。看见什么&#xff0c;它就是什么。 常量有下面这几种&#xff1a; 数字常量&#xff08;数值常量&#xff09; 字符串常量 布尔常量 自定义…

Vol.34 Good Men Project:一个博客网站,每月90万访问量,通过付费订阅和广告变现

今天给大家分享的案例网站是&#xff1a;Good Men Project&#xff0c;这是一个专门针对男性成长的博客网站&#xff0c;内容包括人际关系、家庭、职业发展等话题。 它的网址是&#xff1a;The Good Men Project - The Conversation No One Else Is Having 流量情况 我们先看…

高分卫星助力台湾省花莲县地震应急救援

4月3日7时58分&#xff0c;在台湾省花莲县海域&#xff08;北纬23.81度&#xff0c;东经121.74度&#xff09;发生7.3级地震&#xff0c;震源深度12公里。接中国地震局地震预测研究所应急需求&#xff0c;国家航天局对地观测与数据中心&#xff08;以下简称“中心”&#xff09…

C#仿OutLook的特色窗体设计

目录 1. 资源图片准备 2. 设计流程&#xff1a; &#xff08;1&#xff09;用MenuStrip控件设计菜单栏 &#xff08;2&#xff09;用ToolStrip控件设计工具栏 &#xff08;3&#xff09;用StatusStrip控件设计状态栏 &#xff08;4&#xff09;ImageList组件装载树节点图…

SQLyog连接数据库8.0版本解析错误问题解决方案

问题描述&#xff1a; 解决方案&#xff1a; alter userrootlocalhostidentified with mysql_native_password by 密码; 再次连接就可以了。

实现顺序表的增删查改

现在让我们探索数据结构这个美妙的世界吧&#xff01; 概念介绍 线性表是具有相同特性的数据元素的有限序列。线性表是一种在实际运用中广泛运用的线性结构&#xff0c;如线性表&#xff0c;栈&#xff0c;队列&#xff0c;字符串等。 顺序表的本质是数组&#xff0c;实现了…

js的事件冒泡、捕获、委托

事件不仅存在js中&#xff0c;也存在在其他语言中&#xff0c;js事件背后的主要思想是能够在特定事件发生时运行代码。 先普及一个概念&#xff0c;什么是事件处理程序&#xff1f; 事件处理程序就像一个特殊的通用遥控器&#xff0c;可以执行某些操作&#xff0c;例如更改电…

java自动化-03-04java基础之数据类型举例

1、需要特殊注意的数据类型举例 1&#xff09;定义float类型&#xff0c;赋值时需要再小数后面带f float num11.2f; System.out.println(num1);2&#xff09;定义double类型&#xff0c;赋值时直接输入小数就可以 3&#xff09;另外需要注意&#xff0c;float类型的精度问题…

鸿蒙开发就业前景到底怎么样?

随着科技的不断进步&#xff0c;鸿蒙操作系统的推出为开发者们带来了新的机遇和挑战。鸿蒙&#xff0c;作为华为自主研发的操作系统&#xff0c;旨在为消费者提供更为流畅、安全的智能设备体验。那么&#xff0c;鸿蒙开发就业前景如何呢&#xff1f; 一、鸿蒙操作系统的优势 …

探索--------------redis缓存三大问题及解决方案

目录 一、redis的三大缓存问题 1、缓存穿透 1.1 问题描述 1.2缓存穿透发生的条件 1.3缓存穿透发生的原因 1.4解决方案 2、缓存雪崩 2.1问题描述 2.2解决缓存雪崩问题的方法有&#xff1a; 3、缓存击穿 &#xff08;热点数据集中失效&#xff09; 3.1问题描述 3.2缓…

SpringBoot快速入门笔记(3)

文章目录 一、MybatisPlus1、ORM2、添加依赖3、全局配置4、Navicat5、UserController6、CRUD操作7、BaseMapper8、两个注解 二、多表查询1、模拟用户订单2、通过用户查相关订单3、UserMapperNew4、查询订单和所属用户5、OrderMapper6、OrderController 三、条件查询四、分页查询…

【Ubuntu】用 VMware 安装 macOS

本教程使用 Ubuntu 20.04.6 LTS&#xff0c;VMware Workstation Pro 17.5.1&#xff0c;macOS Sonoma 14.4。文中所有需要的下载链接均以 Markdown 的形式体现在文字上。 下载 VMware Workstation Pro&#xff0c;目前最新版本是 17.5.1。 使用密钥&#xff0c;进行破解。 VM…

金融中的数学知识

随机偏微分方程相比普通偏微分方程具有额外的随机项&#xff0c;反映了其描述的现象具有随机性质

【核弹级安全事件】XZ Utils库中发现秘密后门,影响主要Linux发行版,软件供应链安全大事件

Red Hat 发布了一份“紧急安全警报”&#xff0c;警告称两款流行的数据压缩库XZ Utils&#xff08;先前称为LZMA Utils&#xff09;的两个版本已被植入恶意代码后门&#xff0c;这些代码旨在允许未授权的远程访问。 此次软件供应链攻击被追踪为CVE-2024-3094&#xff0c;其CVS…

中国大学生计算机设计大赛—软件应用与开发赛道—赛后感想

1.比赛介绍 中国大学生计算机设计大赛是我国高校面向本科生最早的赛事之一&#xff0c;是全国普通高校大学生竞赛排行榜榜单赛事之一。自2008年开赛至2019年&#xff0c;一直由教育部高校与计算机相关教指委等或独立或联合主办。大赛的目的是以赛促学、以赛促教、以赛促创&…

一些题目学习

1.打开文件添加helloworld public class Saier {public static void main(String[] args){String path"C:\\Users\\sjg\\Desktop\\abc.txt";String text"hello world";try {File file new File(path);FileWriter fileWriter new FileWriter(file,true);…

阴影画图转html

深受启发 https://segmentfault.com/a/1190000014943400?utm_sourcetag-newest https://gitee.com/yun-36/shadow-drawing 通过File对象&#xff0c;读成dataURL&#xff0c;生成图片&#xff0c;挂到canvas&#xff0c;生成图片文件对应的rgba数据像素点信息&#xff0c;处理…

ideaSSM 校园兼职招聘平台bootstrap开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 idea 开发 SSM 校园兼职招聘平台是一套完善的信息管理系统&#xff0c;结合SSM框架和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff…

探索设计模式的魅力:揭秘B/S模式在AI大模型时代的蜕变与进化

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL应用》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自热榜文章&#xff1a;探索设计模式的魅力&#xff1a;揭秘B/S…