首页 > 其他分享 >设计模式——单例模式

设计模式——单例模式

时间:2024-06-18 18:27:48浏览次数:23  
标签:Singleton include 创建 模式 实例 线程 单例 设计模式

单例模式(Singleton)

单例模式是一种创建型设计模式, 保证一个类只有一个实例,并提供一个访问它的全局访问点

单例模式解决了两个问题:

  1. 保证一个类只有一个实例。控制类的实例数量的常见原因是控制某些共享资源(例如数据库或文件)的访问权限。
  2. 为该实例提供一个全局访问节点
    但是单例模式违反了单一职责原则

在这里插入图片描述

单例模式的实现方式

  1. 在类中添加一个私有静态成员变量用于保存单例实例
  2. 声明一个共有静态构造方法用于获取单例实例
  3. 在静态方法中实现"延迟初始化"。 该方法会在首次被调用时创建一个新对象, 并将其存储在静态成员变量中。 此后该方法每次被调用时都返回该实例。
  4. 将类的构造函数设为私有。类的静态方法仍能调用构造函数,但是其他对象不能调用
  5. 检查客户端代码,将对单例的构造函数的调用替换为对其静态构建方法的调用。
#include <iostream>
#include <string>

using namespace std;

class Singleton
{
public:
    // 本类实例的唯一全局访问点
    static Singleton &GetInstance()
    {
        // 如果实例不存在,创建实例
        if (instance == nullptr)
        {
            instance = new Singleton();
            cout << "首次创建实例" << endl;
        }
        else
        {
            cout << "重复创建,返回同一个实例" << endl;
        }

        return *instance;
    }

    ~Singleton(){
        delete instance;
        instance = nullptr;
    }

private:
    // 私有的静态指针,指向单例
    static Singleton *instance;
    // 私有的构造函数,防止外部代码创建类的实例
    Singleton() {}
};

// 静态成员需要在类外定义
Singleton *Singleton::instance = nullptr;

// 客户端代码
static void Client()
{
    Singleton &s1 = Singleton::GetInstance();
    Singleton &s2 = Singleton::GetInstance();
    Singleton &s3 = Singleton::GetInstance();
}

int main()
{
    Client();

    return 0;
}

输出为

首次创建实例
重复创建,返回同一个实例
重复创建,返回同一个实例

多线程时候的单例模式

如果main函数改写成

int main()
{
    // 线程容器
    vector<thread> threads;

    // 创建100个线程
    for (int i = 0; i < 10; i++){
        threads.emplace_back(Client);
    }
    // 等待所有线程完成
    for(auto& th:threads){
        th.join();
    }

    return 0;
}

注意将s2和s3注释掉了
输出变为

重复创建,返回同一个实例首次创建实例重复创建,返回同一个实例
首次创建实例
首次创建实例

重复创建,返回同一个实例
重复创建,返回同一个实例
重复创建,返回同一个实例

重复创建,返回同一个实例重复创建,返回同一个实例

出现了线程安全问题,有多个线程首次创建了实例
原因是GetInstance方法中条件判断(if (instance == nullptr))和实例创建(instance = new Singleton();)之间的代码片段并不是原子操作,可能会有多个线程同时进入这个条件判断,并且创建多个实例。

加锁的版本

class Singleton
{
public:
    // 本类实例的唯一全局访问点
    static Singleton &GetInstance()
    {
        // 加锁
        lock_guard<mutex> lock(mutex_);
        // 如果实例不存在,创建实例
        if (instance == nullptr)
        {
            instance = new Singleton();
            cout << "首次创建实例" << endl;
        }
        else
        {
            cout << "重复创建,返回同一个实例" << endl;
        }

        return *instance;
    }

    ~Singleton()
    {
        delete instance;
        instance = nullptr;
    }

private:
    // 私有的静态指针,指向单例
    static Singleton *instance;
    // 私有的构造函数,防止外部代码创建类的实例
    Singleton() {}
    // 锁
    static mutex mutex_;
};

// 静态成员需要在类外定义
Singleton *Singleton::instance = nullptr;
mutex Singleton::mutex_;

加锁解决了线程安全的问题,但是新的问题来了,不管三七二十一都加锁,这样很影响性能

双重检查锁

#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <mutex>

using namespace std;

class Singleton
{
public:
    // 本类实例的唯一全局访问点
    static Singleton &GetInstance()
    {
        // 如果实例不存在,创建实例
        if (instance == nullptr)
        {
            // 加锁
            lock_guard<mutex> lock(mutex_);
            // 第二次检查
            if (instance == nullptr)
            {
                instance = new Singleton();
                cout << "首次创建实例" << endl;
            }
        }
        else
        {
            cout << "重复创建,返回同一个实例" << endl;
        }

        return *instance;
    }

    ~Singleton()
    {
        delete instance;
        instance = nullptr;
    }

private:
    // 私有的静态指针,指向单例
    static Singleton *instance;
    // 私有的构造函数,防止外部代码创建类的实例
    Singleton() {}
    // 锁
    static mutex mutex_;
};

// 静态成员需要在类外定义
Singleton *Singleton::instance = nullptr;
mutex Singleton::mutex_;


// 客户端代码
static void Client()
{
    Singleton &s1 = Singleton::GetInstance();
    // Singleton &s2 = Singleton::GetInstance();
    // Singleton &s3 = Singleton::GetInstance();
}


int main()
{
    // 线程容器
    vector<thread> threads;

    // 创建100个线程
    for (int i = 0; i < 10; i++){
        threads.emplace_back(Client);
    }
    // 等待所有线程完成
    for(auto& th:threads){
        th.join();
    }

    return 0;
}

饿汉式

上面的代码是单例模式的懒汉式。所谓懒汉式,就是在程序需要用到这个类的实例的时候采取创建,所以在多线程的场景下存在线程安全问题。

饿汉式,是指在程序在首次加载类的时候就会实例化,所以不存在线程安全问题。

代码如下

// 饿汉式实现
#include <iostream>  
#include <string>  
#include <thread>  
#include <vector>  
  
using namespace std;  
  
class Singleton  
{  
public:  
    // 本类实例的唯一全局访问点  
    static Singleton &GetInstance()  
    {  
        // 直接返回已经初始化的实例  
        return instance;  
    }  
  
    // 禁止拷贝构造函数和赋值操作符  
    Singleton(const Singleton&) = delete;  
    Singleton& operator=(const Singleton&) = delete;  
  
private:  
    // 私有的静态实例,在类声明时初始化  
    static Singleton instance;  
  
    // 私有的构造函数,防止外部代码创建类的实例  
    Singleton()  
    {  
        cout << "首次创建实例" << endl;  
    }  
};  
  
// 静态成员需要在类外定义  
Singleton Singleton::instance;  
  
// 客户端代码  
static void Client()  
{  
    Singleton &s = Singleton::GetInstance();  
}  
  
int main()  
{  
    // 线程容器  
    vector<thread> threads;  
  
    // 创建10个线程  
    for (int i = 0; i < 10; i++)  
    {  
        threads.emplace_back(Client);  
    }  
  
    // 等待所有线程完成  
    for(auto& th:threads)  
    {  
        th.join();  
    }  
  
    return 0;  
}

标签:Singleton,include,创建,模式,实例,线程,单例,设计模式
From: https://blog.csdn.net/weixin_42903300/article/details/139780199

相关文章

  • C# 模式匹配
    C#模式匹配https://www.geeksforgeeks.org/pattern-matching-in-c-sharp/https://www.codeproject.com/Articles/5368148/Your-Quick-Guide-to-Pattern-Matching-in-Csharp什是模式匹配模式匹配是一种在代码中识别和提取数据的机制。它允许您以声明式的方式检查对象的形状......
  • 设计模式-策略模式
    策略模式策略模式,又叫政策模式,它是将定义的算法家族分别封装起来,让他们之间可以互相替换,从而让算法的变化不会影响到使用算法的用户,属于行为型模式。角色:上下文角色(Context):用来操作策略的上下文环境,屏蔽高层模块对策略,算法的直接访问,封装可能存在的变化。抽象策略角色(Strateg......
  • Golang与设计模式
    单例模式因为之前研究Java比较多,所以当我试着使用go来实现一些设计模式的时候,首先想到的就是照搬Java中的思路,后面对go了解加深之后又增加了一些新的思路。在Java中实现的单例模式的思路有很多,但是比较好的两个思路是利用类加载机制生成单例对象,check-lock-check机制避免并发问......
  • Ps:条件模式更改
    Ps菜单:文件/自动/条件模式更改Automate/ConditionalModeChange条件模式更改 ConditionalModeChange脚本命令可用于将图像文档的颜色模式更改为指定的模式。既可以将指定的源颜色模式转换为目标颜色模式,还可以将此命令记录在动作中以便实施快速转换。◆  ◆  ◆......
  • ARMv7 寄存器 工作模式 和指令集 和 堆栈回溯
    因此,在图4-1中,如果处理器是在IRQ模式,我们可以看见R0,R1...R12(与在用户模式看到的相同的寄存器),加上SP_IRQ和LR_IRQ(仅在IRQ模式中可以访问的寄存器)和R15(程序计数器,PC)。我们通常不必指定模式中的寄存器名。如果我们在一行代码中引用R13,处理器会访问当前模式对应的SP寄存器。......
  • 持续总结中!2024年面试必问 20 道设计模式面试题(二)
    上一篇地址:持续总结中!2024年面试必问20道设计模式面试题(一)-CSDN博客三、请描述单例模式(SingletonPattern)及其使用场景。单例模式是一种创建型设计模式,用于确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这种模式在软件系统中非常常见,因为它提供了一种控制实......
  • SingletonKit单例源码阅读学习
    阅读学习QFramwork中的SingletonKit源码。Singleton普通类的单例作为最常用的单例模块,通过继承单例泛型类来实现,需要私有构造;//使用第一种接口单例方式internalclassClass2Singleton:Singleton<Class2Singleton>{//记录被初始化的次数privat......
  • MVVM模式开发WinForm-ReactiveUI
    一、引言  谈到MVVM设计模式,首先让人想到的是WPF。没错,MVVM的概念是微软在2005年提出的,并首次将其应用到WPF中。最近非常火的Vue.js也是使用了MVVM的设计模式。MVVM设计模式的核心部分是DataBinding机制。顾名思义,其功能就是将Model的数据绑定到View层,并且将View层控件的变换绑......
  • 华为eNSP实验:Eth-Trunk LACP模式
    华为eNSP实验中的Eth-TrunkLACP模式是一种高级网络配置,旨在通过聚合多个物理链路来创建一个逻辑链路,以增加带宽和提供链路冗余。LACP(LinkAggregationControlProtocol)是一个关键的协议,它支持这种链路聚合技术的动态分配和调整。下面将深入探讨Eth-TrunkLACP模式的配置和......
  • [图解]《分析模式》漫谈07-反射,不是映射
    100:00:00,780-->00:00:04,910今天我们来说一个反射不是映射的问题200:00:07,220-->00:00:11,300在第2章里面有这么一段话300:00:11,550-->00:00:14,730这里提到两个词400:00:14,740-->00:00:16,050一个是reflection500:00:17,560-->00:00:18,490一个......