当前位置: 首页 > news >正文

【单例模式】

概述

一个类不管创建多少次对象,永远只能得到该类型的一个对象的实例。

常用到的比如日志模块 ,数据库模块

    饿汉:在类加载时就创建单例对象,因此它是线程安全的,因为对象的创建在程序启动时就已经完成,不存在多线程同时创建对象的问题。
懒汉:在第一次使用单例对象时才创建对象,这种方式在多线程环境下需要考虑线程安全问题,通常使用互斥锁来保证对象只被创建一次。

1. 一个私有构造函数(确保只能单例类自己创建实例):
单例类通常会将其构造函数设为私有,以防止外部代码直接实例化对象。

2. 一个私有静态变量(确保只有一个实例)
单例类通常包含一个私有的静态变量,用于保存该类的唯一实例。

3. 一个公有静态函数(给使用者提供调用方法)

单例模式的6种实现

1、懒汉式(线程不安全)

class Singleton {
private:// 延迟初始化:初始为 nullptr,第一次使用时创建static Singleton* instance;// 私有构造:禁止外部 newSingleton() {}// 禁用拷贝(防止复制实例)Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;public:// 全局访问点static Singleton* getInstance() {if (instance == nullptr) {instance = new Singleton(); // 第一次调用时创建实例}return instance;}
};// 静态成员类外初始化
Singleton* Singleton::instance = nullptr;

先不创建实例,当第一次被调用时,再创建实例,所以被称为懒汉式。

优点:延迟了实例化,如果不需要使用该类,就不会被实例化,只有在需要时才创建实例,避免了资源浪费。

缺点:线程不安全,多线程环境下,如果多个线程同时进入了` if (instance == null) `,若此时还未实例化,也就是`instance == null`,那么就会有多个线程执行 `instance = new Singleton(); `,就会实例化多个实例;

2、饿汉式(线程安全)

class Singleton {
private:static Singleton instance;Singleton() {}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;public:static Singleton& getInstance() {return instance;}
};Singleton Singleton::instance;      // 静态成员类外初始化(程序启动时执行)

先不管需不需要使用这个实例,直接先实例化好实例(饿死鬼一样,所以称为饿汉式),然后当需要使用的时候,直接调方法就可以使用了

优点:提前实例化好了一个实例,避免了线程不安全问题的出现,

缺点:直接实例化了实例,不再延迟实例化;若系统没有使用这个实例,或者系统运行很久之后才需要使用这个实例,都会使操作系统的资源浪费。

3、懒汉式(线程安全)

class Singleton {
private:static Singleton* instance;static std::mutex mtx; // 互斥锁:保证线程安全Singleton() {}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;public:// 整个方法加锁:每次调用都需获取锁static Singleton* getInstance() {std::lock_guard<std::mutex> lock(mtx); // 自动加锁/解锁,避免死锁if (instance == nullptr) {instance = new Singleton();}return instance;}
};// 静态成员初始化
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

实现和线程不安全的懒汉式 几乎一样,唯一不同的点是,在get方法上 加了一把锁。如此一来,多个线程访问,每次只有拿到锁的的线程能够进入该方法,避免了多线程不安全问题的出现。

优点:延迟实例化,节约了资源,并且是线程安全的。

缺点:虽然解决了线程安全问题,但是性能降低了。因为,即使实例已经实例化了,既后续不会再出现线程安全问题了,但是锁还在,每次还是只能拿到锁的线程进入该方法使线程阻塞,等待时间过长。

4、双重检查锁实现(线程安全)

class Singleton {
private:// volatile确保多线程下实例状态可见static volatile Singleton* instance;static std::mutex mtx;Singleton() {}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;public:// 双重检查锁定实现线程安全的懒加载static Singleton* getInstance() {// 第一次检查:避免频繁加锁if (instance == nullptr) {// 加锁:确保只有一个线程进入初始化std::lock_guard<std::mutex> lock(mtx);// 第二次检查:防止多线程同时通过第一次检查if (instance == nullptr) {instance = new Singleton();}}return const_cast<Singleton*>(uniqueInstance);}
};// 静态成员初始化
volatile Singleton* Singleton::uniqueInstance = nullptr;
std::mutex Singleton::mtx;

双重检查锁相当于是改进了线程安全的懒汉式。线程安全的懒汉式的缺点是性能降低了,造成的原因是因为即使实例已经实例化,依然每次都会有锁。

而现在,我们将锁的位置变了,并且多加了一个检查。也就是,先判断实例是否已经存在,若已经存在了,则不会执行判断方法内的有锁方法了。 而如果,还没有实例化的时候,多个线程进去了,也没有事,因为里面的方法有锁,只会让一个线程进入最内层方法并实例化实例。如此一来,最多最多,也就是第一次实例化的时候,会有线程阻塞的情况,后续便不会再有线程阻塞的问题。

volatile 的作用

`new Singleton()` 底层分 3 步:分配对象 -> 初始化对象 -> 指针指向内存

编译器/CPU可能重排上述顺序,1->3->2,导致B拿到未初始化的实例,`volatile` 禁止重排,确保“初始化完成后才能赋值指针”,同时保证对线程对`instance`的读写可见

5、静态内部类的实现

class Singleton {
private:// 私有构造Singleton() {}// 禁用拷贝Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;// 静态内部类:仅在被访问时加载static class SingletonHolder {public:// 内部类静态成员:程序启动时初始化(线程安全)static Singleton instance;};public:// 全局访问点:调用时触发内部类加载static Singleton& getInstance() {return SingletonHolder::instance;}
};// 静态内部类的静态成员初始化
Singleton Singleton::SingletonHolder::instance;

延迟加载:外部类 `Singleton` 加载时,内部类 `SingletonHolder` 不加载;仅调用 `getInstance()` 时,内部类才加载并初始化 `instance`。
线程安全:C++ 静态成员初始化在单线程阶段执行,天然避免并发问题

缺点:无法主动销毁实例(实例随程序退出释放)

6、枚举类(线程安全 )

// 枚举类:默认线程安全,且天然防止拷贝
enum class Singleton {INSTANCE; // 唯一枚举实例// 枚举类成员方法(扩展功能)void doSomething() {std::cout << "Singleton 执行任务" << std::endl;}
};// 全局访问宏(可选,简化调用)
#define SINGLETON Singleton::INSTANCE

枚举类的三个特点:
1. 成员唯一:枚举里的每个成员(INSTANCE)是全局唯一的,整个程序只有一个,不能像普通类那样new多个对现象
2. 禁止拷贝
3. 自动初始化:枚举的成员会在程序启动时自动初始化(饿汉),无需手动创建

使用场景

(1)频繁实例化然后又销毁的对象,使用单例模式可以提高性能

(2)经常使用的对象,但实例化时耗费时间或者资源多,如数据库连接池,使用单例模式,可以提高性能,降低资源损坏

(3)使用线程池之类的控制资源时,使用单例模式,可以方便资源之间的通信

具体如:日志,数据库连接池,计数器等

http://www.dtcms.com/a/353939.html

相关文章:

  • CUDA的编译与调试
  • Mac 上录制视频有几种常见方式
  • 基于springboot的校园资料分享平台(源码+论文+PPT答辩)
  • 网络安全监控中心
  • 【笔记】Windows 安装 Triton 的工作记录(之二)
  • IDR的RWA金融逻辑RWA:全球金融革命的底层协议
  • 数学建模——马尔科夫链(Markov Chain Model)
  • 集成学习之 Stacking(堆叠集成)
  • django配置多个app使用同一个static静态文件目录
  • 使用openCV(C ++ / Python)的Alpha混合
  • 【高级机器学习】 2. Loss Functions(损失函数)
  • 一、快速掌握Python 中的文件操作知识体系
  • mysql zip包安装步骤
  • 2025(秋)中国国际健康产业(成都)博览会:探索健康未来辉煌
  • TCP 并发服务器构建
  • 场外期权能做套利吗?
  • 二叉树的工程实践与高频问题(续):从LeetCode真题到系统设计的深度剖析
  • centos7 安装指定版本的fastfds
  • 了解CDC(变更数据捕获)如何革新数据集成方式
  • Linux 系统调优工具与实践指南
  • 个人博客系统系统---测试报告
  • HarmonyOS布局实战:用声明式UI构建自适应电商卡片
  • 【源码分析】@vue/runtime-dom/src/apiCustomElement.ts 解析
  • 重磅升级,Pixso 2.0赋能HarmonyOS应用设计与开发
  • 安卓11 12系统修改定制化_____如何修改固件 实现给指定内置的应用无障碍权限
  • Sybase 安装与备份
  • 【c++】超好玩游戏
  • 一、CSS3 新增选择器(非 “属性”,但为核心基础)
  • day082-初识ElasticStack
  • 路由基础(二):路由表和FIB表