实战设计模式之单例模式

概述

在进行大型项目的系统架构设计时,确保某些类只有一个实例,是非常重要的。比如:日志记录器、数据库连接池、配置管理器等组件,通常只需要一个实例来处理所有请求。在这种情况下,如果每次使用都创建新的对象实例,不仅会浪费系统资源,还可能导致数据不一致。为了解决这一问题,单例模式应运而生。

基本原理

单例模式的核心在于控制类的实例化过程,确保在整个应用程序生命周期内只存在一个实例,并提供一个全局访问点。为了达到这个目的,单例模式通常包括以下几个核心特征。

私有化构造函数:防止外部代码直接调用构造函数创建新实例。

静态成员变量:用于存储唯一的类实例。

公共静态方法:提供获取唯一实例的方法,通常是Singleton函数或GetInstance函数。

懒加载机制:只有当需要时才创建实例,以提高性能。

线程安全

需要特别注意的是,单例模式在多线程环境下可能会出现问题。具体来说,如果两个线程几乎同时调用了 Singleton函数或GetInstance函数,那么就可能出现两个线程都判断静态成员变量为NULL,并分别创建新实例的情况,从而违反了单例模式的基本要求。

为了避免这种情况,我们可以引入线程同步机制。一种常见做法是在Singleton函数或GetInstance函数中使用互斥锁,以确保同一时间只有一个线程能够进入临界区。然而,这样做会带来额外的性能开销,尤其是在高并发场景下。因此,更推荐的做法是采用双重检查锁定技术,它可以在保持线程安全的同时尽量减少锁的竞争。具体如何使用,可以参考下面的示例代码。

cpp 复制代码
#include <iostream>
#include <mutex>

using namespace std;

class CLogger
{
public:
    static CLogger *Singleton()
    {
        if (s_pSingleton == NULL)
        {
            // 第一次检查
            lock_guard<mutex> lock(s_mutexLog);
            if (s_pSingleton == nullptr)
            {
                // 第二次检查
                s_pSingleton = new CLogger();
            }
        }

        return s_pSingleton;
    }

    void Info(const string& strMsg)
    {
        cout << "Info: " << strMsg << endl;
    }

private:
    CLogger() {}

    static CLogger* s_pSingleton;
    static mutex s_mutexLog;
};

CLogger* CLogger::s_pSingleton = NULL;
mutex CLogger::s_mutexLog;

int main()
{
    CLogger::Singleton()->Info("Hello, Hope_Wisdom");
    return 0;
}

在上面的示例代码中,我们实现了一个可多线程使用的日志单例类。通过两次检查s_pSingleton是否为NULL,我们确保了即使多个线程同时到达临界区,也只会有一个线程真正执行创建实例的操作。

实战解析

假如我们有很多个类都需要实现单例功能,那么,每个类都像上面的日志类CLogger那样去实现,将是非常繁琐而冗余的。能否编写一个单例基类,其他需要实现单例功能的类,都从该基类派生呢?

答案是肯定的。实现一个通用的单例模式基类,可以让其他类继承它以获得单例行为,这是一种优雅且可复用的设计方法。在C++中,我们可以通过模板来创建一个泛型的单例基类,这样可以确保任何派生类都能自动具备单例特性。

除了创建实例外,单例模式还需要考虑如何正确地销毁实例,以避免内存泄漏。在C++中,由于没有垃圾回收机制,我们必须手动去管理对象的生命周期。对于单例模式,通常有以下两种方式来处理这个问题。

1、全局静态对象:将单例实例声明为全局静态变量,这样它会在程序启动时自动初始化,并在程序结束时自动销毁。这种方式虽然简单易行,但缺乏灵活性,特别是当单例依赖其他资源或其他单例时(无法保证销毁的先后顺序)。

2、动态创建 + 显式销毁:使用new关键字动态分配内存给单例实例,并在适当的时候通过delete来释放。

综合以上这些考虑,我们在下面的BaseSingleton.h文件中实现了一个单例模式的基类模板。它提供了三个静态成员函数:Open函数用于动态创建单例对象,Close函数用于显式销毁单例对象,Singleton函数用于获取单例对象的指针。

在下面的实现中,没有使用锁来保护单例对象,这是我们特意为之的。原因有二:一是可以省去锁的开销;二是创建和销毁的接口相对应,提醒用户在主程序的最开始和结束处调用Open和Close函数。

cpp 复制代码
#pragma once

template<typename Derived>
class CBaseSingleton
{
public:
    static void Open()
    {
        if (s_pSingleton == NULL)
        {
            s_pSingleton = new Derived();
        }
    }

    static void Close()
    {
        if (s_pSingleton != NULL)
        {
            delete s_pSingleton;
            s_pSingleton = NULL;
        }
    }

    static Derived *Singleton()
    {
        return s_pSingleton;
    }

    virtual ~CBaseSingleton() {}

protected:
    CBaseSingleton() {}

    CBaseSingleton(const CBaseSingleton&) = delete;
    CBaseSingleton& operator=(const CBaseSingleton&) = delete;

private:
    static Derived* s_pSingleton;
};

template<typename Derived>
Derived* CBaseSingleton<Derived>::s_pSingleton = NULL;

接下来,我们重新实现了CLogger类。可以看到,CLogger类单例功能的实现非常简单,从CBaseSingleton<CLogger>类派生,并声明下友元类即可。在main函数中,我们在最开始调用了Open函数,然后使用Singleton函数访问单例对象,最后在结束处调用了Close函数。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

#include "BaseSingleton.h"

class CLogger : public CBaseSingleton<CLogger>
{
    friend class CBaseSingleton<CLogger>;
public:
    void Info(const string& strMsg)
    {
        cout << "Info: " << strMsg << endl;
    }

private:
    CLogger() {}
};

int main()
{
    CLogger::Open();
    CLogger::Singleton()->Info("Hello, Hope_Wisdom");
    CLogger::Close();
    return 0;
}
相关推荐
天空属于哈夫克31 天前
企业微信二次开发:精准实现关键词自动回复
架构·企业微信
晨米酱1 天前
AGENTS.md:Agent 的上下文策略层
面试·架构·agent
这个DBA有点耶1 天前
MVCC深入:Read View、版本链与快照读——InnoDB并发控制的内核
数据库·mysql·架构
盖伦发发1 天前
软件工程SOLID 五大设计原则
后端·软件工程
码流子1 天前
高速公路安全监测实践:碰撞监测预警+物联网底座,从感知到处置的闭环
大数据·人工智能·物联网·算法·架构
moMo1 天前
从固定流程到问题路由:让 LangGraph RAG 按需检索
架构
白远山1 天前
上海24小时自助健身房解决方案实战指南与经验分享
java·数据库·架构·需求分析
LorryJovens1 天前
【LAAP科研】双系统具身AGI范式研究——基于LAAP认知架构与Jev概率决策模型的系统性技术调研与范式验证
人工智能·gpt·安全·架构
Cicada1281 天前
库存消息消费的正确性设计——从幂等窗口到批量流水线
分布式·系统架构
JQweryuiop1 天前
中小型游戏陪练工作室数字化管理实践:基于七彩屋电竞护航小程序订单、履约与绩效系统的落地复盘
大数据·游戏·小程序·架构