设计模式入门(三)单例模式

文章目录

前提

最近在实际项目中使用到了设计模式中的单例模式 ,之前也单纯地从理论方面学习过单例模式 ,但是一直没有机会实际应用到项目中,这次从项目入手简单地对单例模式进行总结。

单例模式

概念

单例模式,顾名思义,就是在代码中一个类只有一个实例

它的思想就是: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。

应用场景

单例模式是一种常见的设计模式,适用于以下场景:

  1. 全局资源管理:当应用程序需要一个全局资源(例如日志记录器、配置管理器、数据库连接等)时,可以使用单例模式来确保只有一个实例存在,并且所有部分都共享该实例。
  2. 线程池:在多线程环境中,使用单例模式来管理线程池是一种常见的做法。线程池是一种重要的资源,通过单例模式可以确保所有线程共享相同的线程池实例,并能够在需要时方便地访问。
  3. 缓存管理:在需要使用缓存的情况下,可以使用单例模式来管理缓存。通过单例模式,可以确保只有一个缓存实例存在,所有部分都可以访问并共享该实例,从而提高缓存的利用率和性能。
  4. 日志记录:在应用程序中,日志记录是一项非常重要的功能。使用单例模式来管理日志记录器可以确保所有部分都使用相同的日志记录器实例,并且可以方便地记录应用程序的状态和行为。
  5. 配置管理:在应用程序中,通常需要加载和管理配置信息。使用单例模式来管理配置信息可以确保所有部分都使用相同的配置实例,并且可以方便地访问和修改配置信息。
    总的来说,单例模式适用于需要确保只有一个实例存在,并且所有部分都可以方便地访问和共享该实例的场景。

我这次的应用场景是:程序中有一组寄存器要维护(寄存器在实际中有且只有一组),无论何时我访问这组寄存器,这组寄存器的值和上一次我访问是的值要一样,不会发生变换(除非用户去手动去改变)。

应用

单例模式有两种实现方式:懒汉式和饿汉式

懒汉式和饿汉式的区别就在于:

  1. 懒汉式只有当用户请求实例化对象时,才会初始化对象;而饿汉式在程序一旦运行后,就会自动初始化对象。这就会带来利弊:如果在整个程序运行过程中,我们不需要实例化对象,那么懒汉式就不会初始化对象,也就不会申请额外的空间资源;而饿汉式会自动初始化对象,而整个程序运行过程中我们没有用到这个对象,就会带来额外的空间以及资源开销。这一点在接下来的代码中可以仔细体会。
  2. 而有了上面的这一点可得,没有加锁的懒汉式是线程不安全的,而饿汉式是线程安全的。具体就说:多个线程如果同一时刻要初始化对象,而这个对象只能有一个,那么该听谁的呢?这样的话,每个线程可能都会初始化对象,那么这个单例模式就不是唯一的了。而饿汉式因为程序一开始就会初始化对象,所以不存在这种情况。

懒汉式

所有单例的实现都包含以下两个相同的步骤:

  1. 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符。
  2. 新建一个静态构建方法作为构造函数。 该函数会 "偷偷" 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。

具体来说,就是要有以下要求:

  1. 在类中添加一个私有静态成员变量用于保存单例实例
  2. 声明一个公有静态构建方法用于获取单例实例
  3. 在静态方法中实现"延迟初始化"(懒汉式)。 该方法会在首次被调用时创建一个新对象, 并将其存储在静态成员变量中。 此后该方法每次被调用时都返回该实例
  4. 将类的构造函数设为私有。 类的静态方法仍能调用构造函数, 但是其他对象不能调用

懒汉式代码:

cpp 复制代码
#define N 100
class Singleton
{
public:
    static Singleton* GetInstance()
    {
        if (instance_ == nullptr) {
            printf("只需要申请一次对象\n");
            instance_ = new Singleton;
        }
        return instance_;
    }

private:
    Singleton() {
        register_ = (char*)malloc(sizeof(char) * N);
    }

    ~Singleton();
    
    Singleton(const Singleton& other) = delete;  // 没有拷贝构造
    
    Singleton& operator=(const Singleton& other) = delete;  // 没有拷贝赋值
    
    static Singleton* instance_;

    char* register_;
};

Singleton* Singleton::instance_ = nullptr;  // 类的静态成员变量需要类外初始化


int main()
{
    Singleton* regA = Singleton::GetInstance();

    Singleton* regB = Singleton::GetInstance();
 
    return 0;
}

线程安全的懒汉式:

cpp 复制代码
#define N 100
class Singleton
{
public:
    static Singleton* GetInstance()
    {
        if (instance_ == nullptr) {  
            i_mutex.lock();  // 先加上锁
            if (instance_ == nullptr)  // 再次判断是否为nullptr,因为可能有其他的线程抢占
            {
                printf("第一次申请对象\n");
                instance_ = new Singleton();
            }
            i_mutex.unlock();
        }
        return instance_; 
    }

    char* GetPtr()
    {
        return register_;
    }
private:
    Singleton() {
        printf("调用构造函数");
        register_ = (char*)malloc(sizeof(char) * N);
    }

    ~Singleton();
    
    Singleton(const Singleton& other) = delete;  // 拷贝构造
    
    Singleton& operator=(const Singleton& other) = delete;  // 拷贝赋值
    
    static Singleton* instance_;

    static std::mutex i_mutex;

    char* register_;
};

std::mutex Singleton::i_mutex;
Singleton* Singleton::instance_ = nullptr;

void Thread1() {
    // Following code emulates slow initialization.
    //std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    Singleton* singleton = Singleton::GetInstance();
    std::cout << (void*)singleton->GetPtr() << "\n";
}


void Thread2() {
    // Following code emulates slow initialization.
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    Singleton* singleton = Singleton::GetInstance();
    std::cout << (void*)singleton->GetPtr() << "\n";
}

int main()
{
    std::thread t1(Thread1);
    t1.detach();
    std::thread t2(Thread2);
    t2.join();

    return 0;
}

饿汉式

饿汉式在程序启动时直接初始化对象,申请资源。因此不用"延迟初始化"(延迟初始化在这里的意思就是:只有我们调用了GetInstance()方法时,才会申请资源)。

cpp 复制代码
#define N 100
class Singleton
{
public:
    static Singleton* GetInstance()
    {
        return instance_;
    }

private:
    Singleton() {
        printf("调用构造函数");
        register_ = (char*)malloc(sizeof(char) * N);
    }

    ~Singleton();

    Singleton(const Singleton& other) = delete;  // 拷贝构造

    Singleton& operator=(const Singleton& other) = delete;  // 拷贝赋值

    static Singleton* instance_;

    char* register_;
};


Singleton* Singleton::instance_ = new Singleton; // 直接申请

int main()
{
    Singleton* regA = Singleton::GetInstance();

    Singleton* regB = Singleton::GetInstance();
 
    return 0;
}

参考链接

  1. https://refactoringguru.cn/design-patterns/singleton
相关推荐
老码观察1 分钟前
设计模式实战解读(五):策略模式——干掉 if-else 的优雅方案
java·设计模式·策略模式
MC皮蛋侠客4 分钟前
C++17 多线程系列(一):线程基础——std::thread 完全指南
开发语言·c++·多线程
MC皮蛋侠客28 分钟前
Perf 火焰图深度实战:CPU 性能分析与异常排查完全指南
linux·c++·性能分析·perf·火焰图
熊孩纸的世界你不懂41 分钟前
Qt + SQLite 配置与使用指南
c++·qt
解决问题no解决代码问题1 小时前
设计模式分类介绍
java·开发语言·设计模式
码上有光1 小时前
c++模板进阶知识讲解(对模板的进一步的运用与理解)
java·前端·c++·特化·模板进阶·偏特化
烬羽1 小时前
从 Python List 到 LLM 接口:一条被忽视的 AI 入门捷径
设计模式
IT空门:门主1 小时前
Java 单例模式详解:7 种实现方式 + volatile 原理 + 反射与序列化问题
java·开发语言·单例模式
Zhang~Ling1 小时前
C++ 继承机制详解下:多继承、虚继承与菱形继承底层原理
开发语言·c++·算法
思麟呀1 小时前
C++工业级日志项目(四)日志落地
linux·开发语言·c++·windows