C++设计模式1:单例模式(懒汉模式和饿汉模式,以及多线程问题处理)

2024-08-22 23:44

本文主要是介绍C++设计模式1:单例模式(懒汉模式和饿汉模式,以及多线程问题处理),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

饿汉单例模式

        程序还没有主动获取实例对象,该对象就产生了,也就是程序刚开始运行,这个对象就已经初始化了。 

class Singleton
{
public:~Singleton(){std::cout << "~Singleton()" << std::endl;}static Singleton* get_instance(){return &singleton;}
private:Singleton() {};Singleton(const Singleton& othersingle) = delete;Singleton& operator=(const Singleton& othersingle) = delete;static Singleton singleton;
};
Singleton Singleton::singleton;//类的静态成员变量要在类外定义
int main()
{Singleton* s1 = Singleton::get_instance();Singleton* s2 = Singleton::get_instance();Singleton* s3 = Singleton::get_instance();std::cout << "s1:" << s1 << std::endl;std::cout << "s2:" << s2 << std::endl;std::cout << "s3:" << s3 << std::endl;
}

        显然饿汉模式是线程安全的,因为单例对象的初始化发生在.bss段,和栈无关,而线程的启动依赖于函数,函数需要开辟栈内存,所以是线程安全的。但是饿汉模式也有缺点,如果这个单例类的构造函数过于复杂,包含了线程和数据库等等一系列的初始化过程,需要进行大量操作,就会导致程序启动变慢。

运行结果如下:    三个对象的地址是一样的,说明是同一个对象,并且最后也只是析构了一次。

 懒汉模式

实例对象直到程序中有模块获取它时,才会初始化这个对象。

#include<iostream>
class Singleton
{
public:~Singleton(){std::cout << "~Singleton()" << std::endl;}static Singleton* get_instance(){if (singleton == nullptr){singleton = new Singleton();}return singleton;}
private:Singleton() {};Singleton(const Singleton& othersingle) = delete;Singleton& operator=(const Singleton& othersingle) = delete;static Singleton* singleton;
};
Singleton* Singleton::singleton=nullptr;//类的静态成员变量要在类外定义
int main()
{Singleton* s1 = Singleton::get_instance();Singleton* s2 = Singleton::get_instance();Singleton* s3 = Singleton::get_instance();std::cout << "s1:" << s1 << std::endl;std::cout << "s2:" << s2 << std::endl;std::cout << "s3:" << s3 << std::endl;
}

运行结果。 

         上面这种写法显然是线程不安全的,因为要构造一个单例,构造函数里面可能需要进行大量的操作。这段代码就会产生竞态条件,我们需要通过线程间的互斥操作来解决。

#include<iostream>
#include<memory>
#include<thread>
#include<mutex>
std::mutex mtx;
class Singleton
{
public:~Singleton(){std::cout << "~Singleton()" << std::endl;}static Singleton* get_instance(){std::lock_guard<std::mutex>loc(mtx);if (singleton == nullptr){singleton = new Singleton();}return singleton;}
private:Singleton() {};Singleton(const Singleton& othersingle) = delete;Singleton& operator=(const Singleton& othersingle) = delete;static Singleton* singleton;
};
Singleton* Singleton::singleton=nullptr;//类的静态成员变量要在类外定义
int main()
{Singleton* s1 = Singleton::get_instance();Singleton* s2 = Singleton::get_instance();Singleton* s3 = Singleton::get_instance();std::cout << "s1:" << s1 << std::endl;std::cout << "s2:" << s2 << std::endl;std::cout << "s3:" << s3 << std::endl;
}

         这种写法虽然可以解决问题,但是加锁的位置,对程序的性能损耗较大,每次要先拿到锁才去判断是否为nullptr,如果不是,这把锁就白拿了,换一下加锁的位置。

        

#include<iostream>
#include<memory>
#include<thread>
#include<mutex>
std::mutex mtx;
class Singleton
{
public:~Singleton(){std::cout << "~Singleton()" << std::endl;}static Singleton* get_instance(){if (singleton == nullptr){std::lock_guard<std::mutex>loc(mtx);singleton = new Singleton();}return singleton;}
private:Singleton() {};Singleton(const Singleton& othersingle) = delete;Singleton& operator=(const Singleton& othersingle) = delete;static Singleton* singleton;
};
Singleton* Singleton::singleton=nullptr;//类的静态成员变量要在类外定义
int main()
{Singleton* s1 = Singleton::get_instance();Singleton* s2 = Singleton::get_instance();Singleton* s3 = Singleton::get_instance();std::cout << "s1:" << s1 << std::endl;std::cout << "s2:" << s2 << std::endl;std::cout << "s3:" << s3 << std::endl;
}

        这次加锁位置明显可以减少程序的性能损耗,但是会出现一个问题,假如开始单例是nullptr,一个线程通过if语句,并且拿到了锁,它只是开辟了内存,并且构造了单例对象,但是构造过程没有执行完全,还没有给这个单例对象赋值, 这时候这个单例还是nullptr,另一个线程这时候也可以通过if语句了,因为单例是nullptr,但是它不能构造单例,因为没有拿到锁,这时候第一个线程给单例赋值完成后,释放了锁,第二个线程拿到锁,就又构造了一次单例。

        要解决这个问题也简单,那就是双重if语句判断。

#include<iostream>
#include<memory>
#include<thread>
#include<mutex>
std::mutex mtx;
class Singleton
{
public:~Singleton(){std::cout << "~Singleton()" << std::endl;}static Singleton* get_instance(){if (singleton == nullptr){std::lock_guard<std::mutex>loc(mtx);if (singleton == nullptr){singleton = new Singleton();}}return singleton;}
private:Singleton() {};Singleton(const Singleton& othersingle) = delete;Singleton& operator=(const Singleton& othersingle) = delete;static Singleton* singleton;
};
Singleton* Singleton::singleton=nullptr;//类的静态成员变量要在类外定义
int main()
{Singleton* s1 = Singleton::get_instance();Singleton* s2 = Singleton::get_instance();Singleton* s3 = Singleton::get_instance();std::cout << "s1:" << s1 << std::endl;std::cout << "s2:" << s2 << std::endl;std::cout << "s3:" << s3 << std::endl;
}

运行结果还是一样的。

        如果我们要简化上面的写法呢?我们可以使用到函数静态局部变量的初始化机制,函数静态局部变量在初始化的时候,底层的汇编指令会自动添加上线程互斥的指令,就可以省去我们加锁的步骤了。而且只有当程序主动调用get_instance函数的时候,单例才会被初始化,也省去了我们的nullptr双重判断了。

#include<iostream>
#include<memory>
#include<thread>
#include<mutex>
std::mutex mtx;
class Singleton
{
public:~Singleton(){std::cout << "~Singleton()" << std::endl;}static Singleton* get_instance(){static Singleton singleton;return &singleton;}
private:Singleton() {};Singleton(const Singleton& othersingle) = delete;Singleton& operator=(const Singleton& othersingle) = delete;static Singleton* singleton;
};
int main()
{Singleton* s1 = Singleton::get_instance();Singleton* s2 = Singleton::get_instance();Singleton* s3 = Singleton::get_instance();std::cout << "s1:" << s1 << std::endl;std::cout << "s2:" << s2 << std::endl;std::cout << "s3:" << s3 << std::endl;
}

运行效果一样。 

这篇关于C++设计模式1:单例模式(懒汉模式和饿汉模式,以及多线程问题处理)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1097738

相关文章

C++实现回文串判断的两种高效方法

《C++实现回文串判断的两种高效方法》文章介绍了两种判断回文串的方法:解法一通过创建新字符串来处理,解法二在原字符串上直接筛选判断,两种方法都使用了双指针法,文中通过代码示例讲解的非常详细,需要的朋友... 目录一、问题描述示例二、解法一:将字母数字连接到新的 string思路代码实现代码解释复杂度分析三、

springboot3.4和mybatis plus的版本问题的解决

《springboot3.4和mybatisplus的版本问题的解决》本文主要介绍了springboot3.4和mybatisplus的版本问题的解决,主要由于SpringBoot3.4与MyBat... 报错1:spring-boot-starter/3.4.0/spring-boot-starter-

Gin框架中的GET和POST表单处理的实现

《Gin框架中的GET和POST表单处理的实现》Gin框架提供了简单而强大的机制来处理GET和POST表单提交的数据,通过c.Query、c.PostForm、c.Bind和c.Request.For... 目录一、GET表单处理二、POST表单处理1. 使用c.PostForm获取表单字段:2. 绑定到结

mysql8.0无备份通过idb文件恢复数据的方法、idb文件修复和tablespace id不一致处理

《mysql8.0无备份通过idb文件恢复数据的方法、idb文件修复和tablespaceid不一致处理》文章描述了公司服务器断电后数据库故障的过程,作者通过查看错误日志、重新初始化数据目录、恢复备... 周末突然接到一位一年多没联系的妹妹打来电话,“刘哥,快来救救我”,我脑海瞬间冒出妙瓦底,电信火苲马扁.

在 Spring Boot 中使用异步线程时的 HttpServletRequest 复用问题记录

《在SpringBoot中使用异步线程时的HttpServletRequest复用问题记录》文章讨论了在SpringBoot中使用异步线程时,由于HttpServletRequest复用导致... 目录一、问题描述:异步线程操作导致请求复用时 Cookie 解析失败1. 场景背景2. 问题根源二、问题详细分

解读为什么@Autowired在属性上被警告,在setter方法上不被警告问题

《解读为什么@Autowired在属性上被警告,在setter方法上不被警告问题》在Spring开发中,@Autowired注解常用于实现依赖注入,它可以应用于类的属性、构造器或setter方法上,然... 目录1. 为什么 @Autowired 在属性上被警告?1.1 隐式依赖注入1.2 IDE 的警告:

解决java.lang.NullPointerException问题(空指针异常)

《解决java.lang.NullPointerException问题(空指针异常)》本文详细介绍了Java中的NullPointerException异常及其常见原因,包括对象引用为null、数组元... 目录Java.lang.NullPointerException(空指针异常)NullPointer

Android开发中gradle下载缓慢的问题级解决方法

《Android开发中gradle下载缓慢的问题级解决方法》本文介绍了解决Android开发中Gradle下载缓慢问题的几种方法,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、网络环境优化二、Gradle版本与配置优化三、其他优化措施针对android开发中Gradle下载缓慢的问

Python自动化处理手机验证码

《Python自动化处理手机验证码》手机验证码是一种常见的身份验证手段,广泛应用于用户注册、登录、交易确认等场景,下面我们来看看如何使用Python自动化处理手机验证码吧... 目录一、获取手机验证码1.1 通过短信接收验证码1.2 使用第三方短信接收服务1.3 使用ADB读取手机短信1.4 通过API获取

关于Nginx跨域问题及解决方案(CORS)

《关于Nginx跨域问题及解决方案(CORS)》文章主要介绍了跨域资源共享(CORS)机制及其在现代Web开发中的重要性,通过Nginx,可以简单地解决跨域问题,适合新手学习和应用,文章详细讲解了CO... 目录一、概述二、什么是 CORS?三、常见的跨域场景四、Nginx 如何解决 CORS 问题?五、基