设计模式13-单件模式

news2024/11/24 8:46:48

设计模式13-单件模式

  • 写在前面
    • 对象性能模式
    • 典型模式
      • 1. 单例模式(Singleton Pattern)
      • 2. 享元模式(Flyweight Pattern)
      • 3. 原型模式(Prototype Pattern)
      • 4. 对象池模式(Object Pool Pattern)
      • 5. 延迟初始化(Lazy Initialization)
      • 6. 虚拟代理(Virtual Proxy)
      • 7. 缓存(Caching)
      • 8. 数据传输对象(DTO,Data Transfer Object)
  • 动机
      • 单例模式的定义
      • 单例模式的结构
      • 单例模式的代码推导
      • 2. 线程安全版本,但锁的代价过高
      • 3. 双检查锁,但由于内存读写reorder不安全
      • 4. C++11版本之后的跨平台实现(volatile)
        • volatile关键字
      • 平台限制和使用场景
      • 多线程编程中的问题
      • 推荐的跨平台多线程同步机制
      • 总结
      • 单例模式的应用
      • 单例模式的特点总结

写在前面

对象性能模式

  • 面向对象很好的解决了抽象的问题,但是必不可免的要付出一定的代价(类空间的重复分配等)。对于通常情况来讲,面向对象的成本大多都可以忽略不计。但某些情况面向对象所在的成本必须谨慎处理。
  • 设计模式中的对象性能模式是指那些专注于提高系统性能、优化资源使用和管理的设计模式。这些模式通过有效的对象创建、共享、复用和管理策略,减少内存占用、提高运行效率,从而提升整体系统性能。

典型模式

  • 单例模式(Singleton Pattern)
  • 享元模式(Flyweight Pattern)
    当然也有一些其他的模式。

设计模式中涉及对象性能优化的模式主要包括以下几种

1. 单例模式(Singleton Pattern)

单例模式确保一个类只有一个实例,并提供全局访问点。这种模式避免了重复创建对象的开销,特别是在需要频繁访问的场景中,如配置类、日志类等。

2. 享元模式(Flyweight Pattern)

享元模式通过共享对象来减少内存使用。它特别适用于有大量相似对象的场景,例如字符处理、图形绘制等。

3. 原型模式(Prototype Pattern)

原型模式通过复制现有对象来创建新对象,从而减少了创建新对象的开销。这种模式适用于创建代价高昂的对象,如大型数据结构、复杂对象等。

4. 对象池模式(Object Pool Pattern)

对象池模式维护一个对象池,复用池中的对象而不是每次都创建和销毁对象。它适用于对象创建和销毁成本较高的情况,如数据库连接、线程等。

5. 延迟初始化(Lazy Initialization)

延迟初始化是指对象的创建或初始化在其实际被使用时才进行。这可以减少程序启动时的资源占用,并将资源分配推迟到实际需要时。

6. 虚拟代理(Virtual Proxy)

虚拟代理是一种代理模式,通过代理对象来控制对实际对象的访问。虚拟代理可以延迟实际对象的创建或初始化,从而优化性能。例如,大型图片的加载可以通过虚拟代理延迟到实际需要时再进行。

7. 缓存(Caching)

缓存模式通过存储之前计算或创建的结果来减少重复计算或创建的开销。这种模式广泛应用于各种性能优化场景,如数据库查询结果缓存、计算结果缓存等。

8. 数据传输对象(DTO,Data Transfer Object)

DTO模式通过一次性传输批量数据来减少多次远程调用的开销。它适用于分布式系统中,通过减少网络通信次数来提高性能。

动机

在软件系统中经常有这样一些特殊的类必须保证他们在系统中只存在一个实例。才能保证他们的逻辑的正确性以及良好的效率。如何绕过常规的构造器提供一种机制来保证一个类只有一个实例呢?让使用者只需使用一个类?这显然不合理。因为这应该是类设计者的责任,而不是使用者的责任。模式就是为了解决这一类问题的模式。

more动机:

  • 在许多应用中,有一些对象需要全局唯一,比如配置文件管理器、日志管理器等。为了确保这些对象在整个应用中只存在一个实例,并且能被全局访问,我们需要一种机制来限制实例化次数并提供统一的访问方式。
  • 单例模式的主要动机是控制对象的实例化过程,确保一个类只有一个实例,并提供一个访问它的全局访问点

单例模式的定义

保证一个类仅有一个实力,并提供一个该实例的全局访问点。

单例模式的结构

结构:

  1. Singleton类:
    • 定义一个静态变量来保存类的唯一实例。
    • 提供一个静态方法用于创建或获取该实例。
    • 将构造函数设为私有,防止外部通过new操作符实例化该类。

以下是单例模式的UML图:

在这里插入图片描述

单例模式的代码推导




class Singleton{
private:
    Singleton();
    Singleton(const Singleton& other);
public:
    static Singleton* getInstance();
    static Singleton* m_instance;
};

Singleton* Singleton::m_instance=nullptr;

//线程非安全版本
Singleton* Singleton::getInstance() {
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}






//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {
    Lock lock;
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}









//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
    
    if(m_instance==nullptr){
        Lock lock;
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
    }
    return m_instance;
}








//C++ 11版本之后的跨平台实现 (volatile)
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    Singleton* tmp = m_instance.load(std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load(std::memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new Singleton;
            std::atomic_thread_fence(std::memory_order_release);//释放内存fence
            m_instance.store(tmp, std::memory_order_relaxed);
        }
    }
    return tmp;
}

这段代码展示了不同版本的单例模式的实现,包括线程不安全版本、线程安全但性能不佳的版本、双检查锁版本以及C++11之后的跨平台实现。以下是对每个版本的详细说明:

### 1. 线程不安全版本

```cpp
class Singleton{
private:
    Singleton();
    Singleton(const Singleton& other);
public:
    static Singleton* getInstance();
    static Singleton* m_instance;
};

Singleton* Singleton::m_instance = nullptr;

Singleton* Singleton::getInstance() {
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

说明:

  • 这个版本中,getInstance方法检查m_instance是否为空,如果为空则创建一个新的Singleton实例。
  • 这种实现方式在单线程环境中是安全的,但在多线程环境中可能导致多个线程同时创建多个实例,违背单例模式的初衷。

2. 线程安全版本,但锁的代价过高

Singleton* Singleton::getInstance() {
    Lock lock;
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

说明:

  • 这个版本使用了锁(例如std::mutex)来确保线程安全。
  • 虽然保证了线程安全,但每次调用getInstance方法都需要获取锁,代价较高,可能影响性能。

3. 双检查锁,但由于内存读写reorder不安全

Singleton* Singleton::getInstance() {
    if (m_instance == nullptr) {
        Lock lock;
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
    }
    return m_instance;
}

说明:

  • 双检查锁的目的是在进入锁之前先检查m_instance是否为空,以减少获取锁的次数,提高性能。
  • 但是,由于编译器和CPU可能对内存读写操作进行重新排序,这种实现方式可能不安全。因为在所有编译器中会对代码进行优化处理,对于new Singleton操作在某些情况下可能会先返回分配空间的指针,后调用构造函数,那么此时第二个线程可能在进行第一个实例判空判断时直接返回指针,但此时这个指针是不可用的,也就是说线程可能看到未完全构造的对象。

4. C++11版本之后的跨平台实现(volatile)

#include <atomic>
#include <mutex>

class Singleton{
private:
    Singleton() {}
    Singleton(const Singleton& other) = delete;
    Singleton& operator=(const Singleton& other) = delete;
public:
    static Singleton* getInstance();
    static std::atomic<Singleton*> m_instance;
    static std::mutex m_mutex;
};

std::atomic<Singleton*> Singleton::m_instance{nullptr};
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    Singleton* tmp = m_instance.load(std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire); // 获取内存fence
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load(std::memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new Singleton;
            std::atomic_thread_fence(std::memory_order_release); // 释放内存fence
            m_instance.store(tmp, std::memory_order_relaxed);
        }
    }
    return tmp;
}

说明:

  • 使用了std::atomicstd::mutex来实现线程安全的单例模式。
  • 通过std::atomic_thread_fence确保内存读写操作的顺序性,防止重排序问题。
  • 在获取锁之前,先尝试加载m_instance,并使用内存屏障(fence)确保正确的内存顺序。
  • 获取锁后再次检查m_instance,如果仍为空则创建实例并存储到m_instance中。
volatile关键字

volatile 关键字在 C++ 中的使用是为了告知编译器该变量可能会被异步地修改(例如,由硬件或其他线程),因此每次访问该变量时都需要重新读取,而不是使用缓存值。

然而,volatile 并不是用于实现线程安全的同步机制。在多线程编程中,volatile 不能保证变量访问的原子性或防止数据竞争。因此,对于跨平台的多线程编程,volatile 并不是一个可靠的选择。

平台限制和使用场景

  1. 硬件访问volatile 常用于访问硬件寄存器或与中断服务例程(ISR)共享的数据。在这些场景中,使用volatile是合适的,因为它能够防止编译器优化掉对这些变量的访问。

  2. 信号处理:在处理异步信号时,可以使用volatile变量标记信号状态。

多线程编程中的问题

在多线程编程中,volatile 并不能保证以下内容:

  1. 原子性:访问volatile变量的操作可能不是原子的。多个线程同时读取和写入volatile变量时,可能会出现竞态条件。
  2. 顺序性volatile 不能防止编译器、CPU 或内存系统对读写操作进行重新排序。因此,多个线程之间的操作顺序可能会混乱。
  3. 可见性:虽然volatile保证变量的最新值总是被读取,但它不能保证一个线程对变量的修改立即对其他线程可见。

推荐的跨平台多线程同步机制

为了实现跨平台的线程安全,应使用 C++11 及以后的标准库提供的原子操作和同步机制:

  1. std::atomic:提供原子操作,确保变量访问的原子性和线程之间的可见性。
  2. 内存顺序(Memory Order):C++11 标准引入了内存模型,通过std::memory_order枚举类型定义内存顺序,以控制内存操作的排序。
  3. 锁(Locks):使用std::mutexstd::lock_guard实现互斥锁,以保护共享数据。
  4. 条件变量(Condition Variables):使用std::condition_variable实现线程间的同步和通信。
    代码同上

总结

  • 线程不安全版本:简单但在多线程环境下不安全。
  • 线程安全版本(锁代价高):线程安全但性能较差,因为每次获取实例都需要获取锁。
  • 双检查锁版本:优化了性能,但存在内存重排序的问题。
  • C++11版本:使用std::atomic和内存屏障,解决了内存重排序的问题,是跨平台的线程安全实现。

通过这些不同版本的实现,我们可以看到单例模式在实际应用中的演变和优化,特别是针对线程安全和性能的考虑。

单例模式的应用

应用:

  • 配置管理: 需要在应用程序中全局共享配置实例。
  • 日志记录: 保证日志记录器实例在整个应用程序中唯一,统一管理日志记录。
  • 数据库连接池: 控制数据库连接池实例的唯一性,方便管理连接。
  • 线程池: 确保线程池实例的唯一性和复用。

单例模式的特点总结

特点总结:

  1. 唯一实例: 确保一个类只有一个实例,并提供全局访问点。
  2. 控制实例化: 通过私有构造函数和静态方法,控制对象的创建和获取。
  3. 延迟实例化: 在需要时才创建实例(懒加载),避免不必要的资源浪费。
  4. 线程安全: 需要特别注意多线程环境下的安全问题,可以通过双重检查锁(Double-Checked Locking)或静态内部类等方式实现线程安全。
  5. 全局访问: 提供一种全局访问点,使得可以方便地访问唯一实例。

单例模式是一种非常常用的设计模式,它在需要全局唯一实例的场景下非常有用。然而,使用单例模式时要注意可能带来的全局状态和并发问题,合理设计和实现才能确保系统的高效和稳定。

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

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

相关文章

WebRTC QoS方法十三.2(Jitter延时的计算)

一、背景介绍 一些报文在网络传输中&#xff0c;会存在丢包重传和延时的情况。渲染时需要进行适当缓存&#xff0c;等待丢失被重传的报文或者正在路上传输的报文。 jitter延时计算是确认需要缓存的时间 另外&#xff0c;在检测到帧有重传情况时&#xff0c;也可适当在渲染时…

无人机图像目标检测技术详解

当前研究领域的热点之一。无人机搭载的高清摄像头能够实时捕获大量图像数据&#xff0c;对这些数据进行有效的目标检测对于军事侦察、环境监测、灾害救援等领域具有重要意义。本文将对无人机图像目标检测技术进行详解&#xff0c;包括图像处理技术、目标检测算法、关键技术应用…

LoFTR关键点特征匹配算法环境构建与图像匹配测试Demo

0&#xff0c;LoFTR CVPR 2021论文《LoFTR: Detector-Free Local Feature Matching with Transformers》开源代码 1&#xff0c;项目主页 LoFTR: Detector-Free Local Feature Matching with Transformers 2&#xff0c;GItHub主页 GitHub - zju3dv/LoFTR: Code for "…

Gen AI核心技术发展趋势分析

Gen AI核心技术解析及发展趋势 判别式模型&#xff0c;适用于处理回归与分类任务&#xff0c;其核心在于精准区分各类数据。与生成模型的生成新数据不同&#xff0c;判别模型专注于揭示输入特征与输出标签之间的紧密联系&#xff0c;从而实现准确分类或预测。在众多应用领域&am…

VS Code打开新文件会覆盖之前的窗口,解决办法

当我在VS Code中打开文件夹时&#xff0c;文件夹中只有一个文件能展示在窗口中&#xff0c;如果点击打开另外一个文件&#xff0c;之前打开的文件又会被覆盖。这样是无法进行文件之间的关联查找的。 要保证窗口可以打开多个文件&#xff0c;有不同的tab显示&#xff0c;设置如下…

好用的电脑屏幕监控软件推荐,什么软件能够监控电脑?

在当今信息化时代&#xff0c;电脑屏幕监控软件成为了企业管理、家长监管以及教育培训等领域的必备工具。通过实时监控电脑屏幕&#xff0c;这类软件可以有效提高工作效率&#xff0c;防止信息泄露&#xff0c;保障网络安全。本文将详细盘点几款主流的电脑屏幕监控软件&#xf…

PHP基础语法(四)

一、字符串类型 1、字符串定义语法 1&#xff09;单引号字符串&#xff1a;在单引号内部&#xff0c;所有的字符都会按照字面意义解释&#xff0c;不会进行变量替换或转义处理&#xff0c;除了 \ 表示单引号本身。 $str1 Hello, World!;2&#xff09;双引号字符串&#xff…

【机器学习算法基础】(基础机器学习课程)-08-决策树和随机森林-笔记

一、决策树之信息论基础 决策树是一种用来做决策的工具&#xff0c;就像我们生活中的选择树。例如&#xff0c;你在选择今天穿什么衣服时&#xff0c;会根据天气情况、出行活动等进行判断。决策树的构建过程涉及一些信息论的概念&#xff0c;用来衡量和选择最好的“分叉点”来进…

Unity打包设置

1.Resolution and Presentation (分辨率和显示) Fullscreen Window (全屏窗口): 应用程序将以全屏窗口模式运行&#xff0c;但不会独占屏幕。适用于想要全屏显示但仍需访问其他窗口的情况。 Resizable Window (可调整大小的窗口): 允许用户调整应用程序窗口的大小。适用于窗口…

Action通信 实践案例

Action通信 Server端实现 创建服务端功能包&#xff08;注意目录层级&#xff09;&#xff1a; ros2 pkg create my_exer05_action_server --build-type ament_cmake --node-name nav_server --dependencies rclcpp rclcpp_action my_exer_interfaces nav_msgs /*需求&#x…

【vim】ubuntu20-server 安装配置 vim 最新最详细

在编程的艺术世界里,代码和灵感需要寻找到最佳的交融点,才能打造出令人为之惊叹的作品。而在这座秋知叶i博客的殿堂里,我们将共同追寻这种完美结合,为未来的世界留下属于我们的独特印记。【vim】ubuntu20-server 安装配置 vim 最新最详细 开发环境一、vim github二、安装必…

基于SpringBoot实现验证码功能

目录 一 实现思路 二 代码实现 三 代码汇总 现在的登录都需要输入验证码用来检测是否是真人登录&#xff0c;所以验证码功能在现在是非常普遍的&#xff0c;那么接下来我们就基于springboot来实现验证码功能。 一 实现思路 今天我们介绍的是两种主流的验证码&#xff0c;一…

IP地址专用SSL/https证书——10分钟签发

一般常用的SSL证书多为域名型SSL证书&#xff0c;即需要提供准确的域名。如果不能提供域名&#xff0c;只能提供IP地址&#xff0c;则需要一种特殊的SSL证书——IP地址证书。下面是IP地址证书的申请教程 IP地址专用SSL证书获取链接https://www.joyssl.com/certificate/select/…

SQL中的LEFT JOIN、RIGHT JOIN和INNER JOIN

在SQL中&#xff0c;JOIN操作是连接两个或多个数据库表&#xff0c;并根据两个表之间的共同列&#xff08;通常是主键和外键&#xff09;返回数据的重要方法。其中&#xff0c;LEFT JOIN&#xff08;左连接&#xff09;、RIGHT JOIN&#xff08;右连接&#xff09;和INNER JOIN…

Open3D 将点云投影到球面

目录 一、概述 二、代码实现 2.1关键函数 2.2完整代码 三、实现效果 3.1原始点云 3.2投影后点云 前期试读&#xff0c;后续会将博客加入下列链接的专栏&#xff0c;欢迎订阅 Open3D点云算法与点云深度学习案例汇总&#xff08;长期更新&#xff09;-CSDN博客 一、概述…

【表单组件】地址组件新增精简模式

07/17 主要更新模块概览 快速筛选 精简模式 触发条件 自定义域名 01 表单管理 1.1 【表单组件】-数据关联组件新增快速筛选功能 说明&#xff1a; 数据关联组件新增快速筛选功能&#xff0c;用户在数据关联组件选择数据时&#xff0c;可以通过快速筛选功能&#xff0…

黑马头条Day07-app端文章搜索

一、今日内容介绍 1. App端搜索效果图 2. 今日内容 &#xff08;1&#xff09;文章搜索 Elasticsearch环境搭建索引库创建文章搜索多条件复合查询索引数据同步 &#xff08;2&#xff09;搜索历史记录 MongoDB环境搭建异步保存搜索历史查看搜索历史列表删除搜索历史 &…

Linux源码安装的Redis如何配置systemd管理并设置开机启动

文章目录 实验前提实验 实验前提 已完成源码安装并能正常启动redis /usr/local/bin/redis-server能正常启动redis 实验 vim /etc/systemd/system/redis.service内容如下&#xff1a; [unit] Descriptionredis-server Afternetwork.target[Service] Typeforking ExecStart/…

从零开始创建vue3项目——包含项目初始化、element-plus、eslint、axios、router、pinia、echarts

项目启动 初始化vue3项目 这里建议先下载pnpm&#xff0c;下载速度更快&#xff0c;如果还没下载可以使用 npm install -g pnpm 如果遇到报错问题&#xff0c;如下 可以在命令行输入下面的指令以切换到淘宝镜像源 npm config set registry https://registry.npm.taobao.org…

Facebook云手机引流运营方法

Facebook&#xff0c;作为全球用户数达到30亿的最大社交媒体平台&#xff0c;汇聚了各类客户群体&#xff0c;蕴藏着巨大的商业潜力。对于外贸电商而言&#xff0c;Facebook是不可或缺的营销平台。一方面&#xff0c;我们可以在Facebook上发布产品信息&#xff0c;寻找并筛选目…