设计模式中单例模式中懒汉模式的问题

news2024/12/23 13:07:33

设计模式中单例模式中懒汉模式的问题

今天在项目中遇到了要使用懒汉模式的问题。百度之后,发现还有很多细节是自己之前没有见过的。于是记录一下。下面是在AI助手中的说明。

单例模式的懒汉模式(Lazy Singleton)是在需要时才创建实例,而不是在程序启动时就创建。懒汉模式常见的实现方式存在一些问题,尤其是在多线程环境中。

下面将详细讲解懒汉模式的优缺点及其问题。

1.懒汉模式的实现通常如下:

class Singleton {
private:
    static Singleton* instance;

    // 私有构造函数,防止外部创建实例
    Singleton() {}

public:
    // 获取实例的公共方法
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }

    // 防止拷贝构造和赋值操作
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

// 静态成员初始化
Singleton* Singleton::instance = nullptr;

懒汉模式的问题

  1. 线程安全问题

    在多线程环境中,懒汉模式存在问题。当多个线程同时调用 getInstance() 方法时,可能会导致多个线程同时判断 instance == nullptrtrue,然后都去创建实例,导致多个实例的创建。为了解决这个问题,可以使用互斥锁(mutex)来保证线程安全,但是这样会降低性能。

修改后的线程安全实现(使用 mutex):

#include <mutex>

class Singleton {
private:
    static Singleton* instance;
    static std::mutex mtx; // 用于保护实例的互斥锁

    Singleton() {}

public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            std::lock_guard<std::mutex> lock(mtx);
            if (instance == nullptr) {
                instance = new Singleton();
            }
        }
        return instance;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

这样可以避免多个线程同时创建多个实例,但性能会受到影响,因为每次获取实例时都需要加锁。在上述实现中,我们使用了双重检查锁定(Double-Checked Locking)模式来提高性能。即第一次检查 instance == nullptr 时不加锁,只有在需要创建实例时才加锁。

2. 使用 std::atomic 确保内存可见性和防止指令重排

C++11 提供了 std::atomic 来确保操作的原子性并避免指令重排。在单例模式中,使用 std::atomic 可以确保对 instance 的写操作对所有线程可见,并且避免因指令重排引发的问题。

为了保证 instance 的初始化过程对所有线程是安全的,我们可以使用 std::atomic 作为 instance 的类型。这样,C++11 保证了原子操作的内存顺序,防止了指令重排问题。

#include <atomic>

class Singleton {
private:
    static std::atomic<Singleton*> instance;

    Singleton() {}

public:
    static Singleton* getInstance() {
        Singleton* temp = instance.load(std::memory_order_acquire);
        if (temp == nullptr) {
            std::lock_guard<std::mutex> lock(mtx);
            temp = instance.load(std::memory_order_relaxed);
            if (temp == nullptr) {
                temp = new Singleton();
                instance.store(temp, std::memory_order_release);
            }
        }
        return temp;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

std::atomic<Singleton*> Singleton::instance{nullptr};

3.多线程性能开销

在多线程环境下,即使是双重检查锁定,也会引入性能开销。锁的争用可能会导致性能瓶颈,尤其是在实例已经创建时,锁的机制会影响到性能。因此,如果创建的实例较为复杂且开销较大,这种性能问题更加明显。

改进方法

  1. 使用 std::call_once
    C++11 提供了 std::call_once,它可以确保某个代码块在多线程中只会被执行一次。使用它可以避免锁的开销。

    改进后的实现:

    cpp#include <mutex>
    
    class Singleton {
    private:
        static Singleton* instance;
        static std::once_flag flag;  // 标记
    
        Singleton() {}
    
    public:
        static Singleton* getInstance() {
            std::call_once(flag, []() {
                instance = new Singleton();
            });
            return instance;
        }
    
        Singleton(const Singleton&) = delete;
        Singleton& operator=(const Singleton&) = delete;
    };
    
    Singleton* Singleton::instance = nullptr;
    std::once_flag Singleton::flag;
    

    这种方式在多线程中比加锁性能更好。

  2. 使用 static 局部变量
    最简单且性能最优的方法是利用 C++ 的静态局部变量特性。C++11 标准保证了静态局部变量的初始化是线程安全的,且只会初始化一次。这是推荐的懒汉模式实现方式。

    cppclass Singleton {
    private:
        Singleton() {}
    
    public:
        static Singleton* getInstance() {
            static Singleton instance;  // 线程安全的静态局部变量
            return &instance;
        }
    
        Singleton(const Singleton&) = delete;
        Singleton& operator=(const Singleton&) = delete;
    };
    

    这种方式不需要显式加锁,且由编译器保证线程安全,并且只会初始化一次,性能非常好。

总结

懒汉模式在多线程环境下需要特别小心,常见的问题包括线程安全问题、内存泄漏、性能开销等。为了确保线程安全并避免性能问题,推荐使用 std::call_once 或 C++11 中的静态局部变量来实现单例模式。通过这些改进,可以大幅度提升懒汉模式的效率和安全性。

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

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

相关文章

算法题(12): 特殊年份

审题&#xff1a; 需要输出特殊年份的个数 思路&#xff1a; 获取数据&#xff1a;用字符串获取&#xff0c;然后全部加到总字符串s上判断 使用for循环对每一个四位数年分进行判断&#xff0c;如果是特殊年份就让负责记录的cou变量 解题&#xff1a; 注意&#xff1a;为什么我们…

RuoYi-Vue 数据权限控制示例nvliz (作业机器版)

目录 需求分析 ​编辑建表 代码编写 service层 Mapper层 测试 修改数据权限 添加数据 需求分析 建表 在若依的数据库中建立设备表&#xff1a;equipment 代码编写 使用代码生成&#xff0c;设备管理信息界面&#xff0c;如下图&#xff1a; 使用RuoYi的代码生成的功…

汽车IVI中控开发入门及进阶(三十八):手机投屏HiCar开发

手机投屏轻松实现手机与汽车的无缝连接,导航、音乐、通话等功能应有尽有,还支持更多第三方应用,让车载互联生活更加丰富多彩。 HiCar在兼容性和开放性上更具优势。 手机投屏可以说是车机的杀手级应用,大大拓宽了车机的可用性范围。其中华为推出的HiCar就是非常好用的一种。…

数据结构经典算法总复习(下卷)

第五章:树和二叉树 先序遍历二叉树的非递归算法。 void PreOrderTraverse(BiTree T, void (*Visit)(TElemType)) {//表示用于查找的函数的指针Stack S; BiTree p T;InitStack(S);//S模拟工作栈while (p || !StackEmpty(S)) {//S为空且下一个结点为空&#xff0c;意味着结束遍…

PID 控制算法理论背景:飞控领域的核心调控机制(1)

在飞控工程领域&#xff0c;PID 控制算法占据着极为关键的地位&#xff0c;是实现飞行器精确稳定控制的基石。PID 作为比例&#xff08;P&#xff09;、积分&#xff08;I&#xff09;、微分&#xff08;D&#xff09;的集成代表&#xff0c;构建起了控制系统的核心架构&#x…

插入排序与计数排序详解

在 C 编程中&#xff0c;排序算法是非常基础且重要的知识。今天我们就来深入探讨两种常见的排序算法&#xff1a;插入排序和计数排序&#xff0c;包括它们的代码实现、时间复杂度、空间复杂度、稳定性分析以及是否有优化提升的空间。 一、插入排序 插入排序&#xff08;Inser…

示波器--UNI-T 优利德 UT4102C 使用介绍

示波器--UNI-T 优利德 UT4102C 使用介绍 1 介绍图示特点 2 UTP04示波器探头&#xff08;100M带宽&#xff09;3 功能介绍4 示例RS232 电平信号测试 参考 1 介绍 图示 特点 2GS/s的实时采样率&#xff1a;设备能够以每秒2吉萨&#xff08;Giga Samples per second&#xff09;…

【Mybatis-Plus】连表查询 逻辑删除 多租户

文章目录 连表查询逻辑删除多租户 连表查询 引入 mybatis-plus-join-boot-starter 依赖 <dependency><groupId>com.github.yulichang</groupId><artifactId>mybatis-plus-join-boot-starter</artifactId><version>1.5.1</version>…

【java面向对象编程】第七弹----Object类、类变量与类方法

笔上得来终觉浅,绝知此事要躬行 &#x1f525; 个人主页&#xff1a;星云爱编程 &#x1f525; 所属专栏&#xff1a;javase &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 一、Object类 1.1equa…

【常微分方程讲义1.1】方程的种类发展与完备

方程在数学历史中不断发展&#xff0c;逐步趋于完备。从最初的简单代数方程到包含函数、算子甚至泛函的更复杂方程&#xff0c;数学家通过不断的扩展和深化&#xff0c;逐渐建立起更为丰富和多元的方程类型体系。方程的种类之所以不断演变&#xff0c;部分是因为解决实际问题的…

《庐山派从入门到...》板载按键启动!

《庐山派从入门到...》板载按键启动&#xff01; 《庐山派从入门到...》板载按键启动&#xff01; 视频内容大致如下 我们之前了解了GPIO的输出模式使用方法&#xff0c;并且成功点灯&#xff0c;很明显本篇要来分享的自然是GPIO的输入模式 正好回顾一下之前学的python基础包…

Android笔试面试题AI答之Android基础(3)

文章目录 1.谈一谈 Android 的安全机制一、系统架构层面的安全设计二、核心安全机制三、其他安全机制与措施 2.Android 的四大组件是哪四大&#xff1f;3.Android 的四大组件都需要在清单文件中注册吗&#xff1f;4.介绍几个常用的Linux命令一、文件和目录管理二、用户和权限管…

黑马商城项目—服务调用

使用起因 之前我们完成了拆分购物车模块,但当我们进行测试查询时: 我们注意到&#xff0c;其中与商品有关的几个字段中:最新状态和库存为默认值,最新价格为空&#xff01;这就是因为我们注释掉了查询购物车时&#xff0c;查询商品信息的相关代码。 那么&#xff0c;我们该如何…

RK356x bsp 5 - 海华AW-CM358SM Wi-Fi/Bt模组调试记录

文章目录 1、环境介绍2、目标3、海华AW-CM358SM3.1、基本信息3.2、支持SDIO3.03.3、电气特性 4、适配流程步骤5、让SDIO控制器工作&#xff0c;且可以扫到WIFI卡5.1、dts配置5.2、验证 6、Wi-Fi 适配6.1、dts配置6.2、驱动移植6.2.1、kernel menuconfig6.2.2、传统驱动移植6.2.…

VBA编程:自定义函数 - 字符串转Hex数据

目录 一、自定义函数二、语法将字符串转换为hex数据MID函数:返回一个字符串中指定位置和长度的子串LEN函数:返回一个字符串的长度(字符数)Asc函数三、定义变量和数据类型变量声明的基本语法常见的数据类型四、For循环基本语法五、&运算符一、自定义函数 定义:用户定义…

工业大数据分析算法实战-day12

文章目录 day12时序分解STL&#xff08;季节性趋势分解法&#xff09;奇异谱分析&#xff08;SSA&#xff09;经验模态分解&#xff08;EMD&#xff09; 时序分割ChangpointTreeSplitAutoplait有价值的辅助 时序再表征 day12 今天是第12天&#xff0c;昨天主要是针对信号处理算…

基于Python Scrapy的豆瓣Top250电影爬虫程序

Scrapy安装 Python实现一个简单的爬虫程序&#xff08;爬取图片&#xff09;_python简单扒图脚本-CSDN博客 创建爬虫项目 创建爬虫项目&#xff1a; scrapy startproject test_spider 创建爬虫程序文件&#xff1a; >cd test_spider\test_spider\spiders >scrapy g…

【Linux系统编程】:信号(2)——信号的产生

1.前言 我们会讲解五种信号产生的方式: 通过终端按键产生信号&#xff0c;比如键盘上的CtrlC。kill命令。本质上是调用kill()调用函数接口产生信号硬件异常产生信号软件条件产生信号 前两种在前一篇文章中做了介绍&#xff0c;本文介绍下面三种. 2. 调用函数产生信号 2.1 k…

腾讯云智能结构化OCR:以多模态大模型技术为核心,推动跨行业高效精准的文档处理与数据提取新时代

&#x1f3bc;个人主页&#xff1a;【Y小夜】 &#x1f60e;作者简介&#xff1a;一位双非学校的大三学生&#xff0c;编程爱好者&#xff0c; 专注于基础和实战分享&#xff0c;欢迎私信咨询&#xff01; &#x1f386;入门专栏&#xff1a;&#x1f387;【MySQL&#xff0…