重温设计模式--10、单例模式

news/2024/12/27 12:36:19/

文章目录

      • 单例模式(Singleton Pattern)概述
      • 单例模式的实现方式及代码示例
        • 1. 饿汉式单例(在程序启动时就创建实例)
        • 2. 懒汉式单例(在第一次使用时才创建实例)
      • 单例模式的注意事项
      • 应用场景
  • C++代码
      • 懒汉模式-经典版(线程不安全)
        • 经典版优化(线程安全)
      • 内部静态变量的懒汉实现
      • 饿汉模式

单例模式(Singleton Pattern)概述

在这里插入图片描述

  1. 定义
    单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。就像是在整个软件系统中,某个特定的对象只能有一个,并且各个部分都能方便地获取到这个唯一的对象。

  2. 作用

    • 资源共享与协调:适用于管理一些全局的资源,比如数据库连接池。整个应用程序通常只需要一个数据库连接池实例来协调和管理数据库连接的分配与回收,避免创建多个连接池导致资源浪费和管理混乱。
    • 状态一致性维护:在某些场景下,需要保证整个系统中某个对象的状态是唯一且一致的。例如,系统配置类,全局只有一份配置信息,各个模块获取的都是同一个配置实例,能保证配置的一致性,防止出现因多个不同配置实例而导致的逻辑混乱。
    • 节省内存和避免重复创建:对于一些创建成本较高或者占用较多系统资源的对象,只创建一个实例可以避免多次重复创建带来的内存消耗和性能开销,像一些复杂的日志记录类,创建实例可能涉及到初始化大量的文件操作相关资源等,单例模式可保证只创建一次。

在这里插入图片描述

单例模式的实现方式及代码示例

1. 饿汉式单例(在程序启动时就创建实例)
#include <iostream>// 饿汉式单例类
class Singleton {
private:// 将构造函数声明为私有,防止外部创建实例Singleton() {std::cout << "创建单例实例" << std::endl;}// 静态成员变量保存唯一实例,在程序启动时就初始化static Singleton* instance;
public:// 获取单例实例的静态方法static Singleton* getInstance() {return instance;}
};// 静态成员变量初始化,在程序启动时就创建好实例
Singleton* Singleton::instance = new Singleton;

以下是使用饿汉式单例的示例代码:

int main() {Singleton* s1 = Singleton::getInstance();Singleton* s2 = Singleton::getInstance();// 比较两个指针,应该指向同一个实例if (s1 == s2) {std::cout << "s1和s2是同一个实例" << std::endl;}return 0;
}

在饿汉式单例中:

  • 优点:实现简单,线程安全(因为在程序启动时就完成了实例的创建,不存在多个线程同时创建实例的竞争问题),在多线程环境下也能保证只有一个实例被创建。
  • 缺点:如果单例类的构造函数执行一些比较耗时或者占用大量资源的初始化操作,并且这个单例可能在程序运行很久之后才会被用到,那么会造成程序启动时不必要的性能开销,提前占用了系统资源。
2. 懒汉式单例(在第一次使用时才创建实例)
#include <iostream>
#include <mutex>// 懒汉式单例类
class Singleton {
private:Singleton() {std::cout << "创建单例实例" << std::endl;}// 静态成员变量保存唯一实例指针static Singleton* instance;// 互斥锁用于保证多线程环境下的线程安全static std::mutex mutex_;
public:// 获取单例实例的静态方法,使用了双重检查锁定(DCLP)来优化线程安全和性能static Singleton* getInstance() {if (instance == nullptr) {std::lock_guard<std::mutex> guard(mutex_);if (instance == nullptr) {instance = new Singleton;}}return instance;}
};// 静态成员变量初始化
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex_;

以下是使用懒汉式单例的示例代码:

int main() {Singleton* s1;Singleton* s2;// 模拟多线程环境下获取单例实例std::thread t1([&]() { s1 = Singleton::getInstance(); });std::thread t2([&]() { s2 = Singleton::getInstance(); });t1.join();t2.join();if (s1 == s2) {std::cout << "s1和s2是同一个实例" << std::endl;}return 0;
}

在懒汉式单例中:

  • 优点:实例在第一次使用时才创建,避免了程序启动时不必要的资源占用和性能开销,对于那些创建成本较高且可能不会马上用到的单例对象比较合适。
  • 缺点:实现相对复杂一些,需要考虑多线程环境下的线程安全问题,虽然使用了双重检查锁定等优化手段,但如果处理不当还是可能出现问题(比如内存乱序执行等情况,不过现代编译器和处理器一般会有相应机制来尽量避免)。

单例模式的注意事项

  1. 构造函数私有:无论是饿汉式还是懒汉式,都要将构造函数声明为私有,这样可以防止外部代码通过常规的方式(如Singleton s;这种直接实例化的语句)来创建多个实例,保证了单例的唯一性。
  2. 线程安全问题:在多线程环境下,懒汉式单例需要特别注意线程安全,要采用合适的同步机制(如互斥锁、原子操作等)来确保在多个线程同时尝试获取实例时,只有一个线程能够创建实例,避免创建出多个实例破坏单例模式的规则。而饿汉式单例天然具有一定的线程安全性,但也需要根据具体应用场景来考虑是否满足需求。
  3. 对象生命周期管理:要注意单例对象的生命周期,尤其是在动态内存分配(如new操作符创建实例)的情况下,需要合理地处理实例的释放,避免内存泄漏等问题。例如,可以通过定义一个静态的析构函数来释放单例对象占用的资源,但这需要谨慎设计,防止出现意外的行为。

单例模式在很多软件系统中都有广泛应用,不过也要根据实际情况合理选择合适的实现方式和注意相关的设计要点,以确保其能正确地发挥作用。

应用场景

  1. 系统配置管理:在一个应用程序中,通常会有各种配置信息,如数据库连接配置、服务器端口配置、应用程序的一些全局参数等。将这些配置信息封装在一个单例的配置类中,整个系统通过唯一的实例来获取和修改配置,保证了配置的一致性,并且方便统一管理。
  2. 日志记录器:用于记录程序运行过程中的各种日志信息,整个程序往往只需要一个日志记录实例来将日志输出到文件、控制台或者发送到远程日志服务器等。不同的模块都向这个唯一的日志记录器实例写入日志,确保日志管理的统一性和有序性。
  3. 线程池管理:在多线程编程中,线程池是管理和复用线程资源的重要组件。一般一个应用程序只需要一个线程池实例,通过这个单例的线程池来分配线程执行任务、回收线程等,避免创建多个线程池导致资源浪费和线程调度混乱。
  4. 缓存机制:例如网页缓存、数据库查询结果缓存等场景。以网页缓存为例,一个网站服务器可以有一个单例的缓存类,用于存储经常访问的网页内容,下次再有相同请求时可以直接从缓存中获取,减少服务器的响应时间和数据库等资源的消耗,而且只有一个缓存实例方便管理缓存的有效性、容量控制等。

C++代码

懒汉模式-经典版(线程不安全)

#include <iostream>
using namespace std;//懒汉模式
class Singleton
{
public:
/**
*需要提供要给全局访问点,就需要在类中定义一个static函数,返回在类内部唯一构造的实例
*/static Singleton *GetInstance(){if (m_Instance == NULL ){m_Instance = new Singleton ();}return m_Instance;}static void DestoryInstance(){if (m_Instance != NULL ){delete m_Instance;m_Instance = NULL ;}}private:
/**
*构造函数卸载私有里,为了防止在外部调用类的构造函数而构造实例
*/Singleton();static Singleton *m_Instance;
};Singleton *Singleton ::m_Instance = NULL;int main(int argc , char *argv [])
{Singleton *singletonObj = Singleton ::GetInstance(); Singleton ::DestoryInstance();return 0;
}
经典版优化(线程安全)
#include <iostream>
using namespace std;//懒汉模式
class Singleton
{
public:
/*
此处进行了两次m_Instance == NULL的判断,是借鉴了Java的单例模式实现时,使用的所谓的“双检锁”机制。
因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,同时也
保证了线程安全。但是,这种实现方法在平时的项目开发中用的很好,也没有什么问题?但是,如果进行大数据
的操作,加锁操作将成为一个性能的瓶颈;为此,一种新的单例模式的实现也就出现了。
*/static Singleton *GetInstance(){if (m_Instance == NULL ){Lock(); if (m_Instance == NULL ){m_Instance = new Singleton ();}UnLock(); }return m_Instance;}static void DestoryInstance(){if (m_Instance != NULL ){delete m_Instance;m_Instance = NULL ;}}
private:Singleton();static Singleton *m_Instance;
};Singleton *Singleton ::m_Instance = NULL;int main(int argc , char *argv [])
{Singleton *singletonObj = Singleton ::GetInstance();Singleton ::DestoryInstance();return 0;
}

内部静态变量的懒汉实现

此方法也很容易实现,在instance函数里定义一个静态的实例,也可以保证拥有唯一实例,在返回时只需要返回其指针就可以了。推荐这种实现方法,真得非常简单。

#include <iostream>
using namespace std;class Singleton
{
public:static Singleton *GetInstance(){lock();static Singleton m_Instance;unlock();return &m_Instance;} 
private:Singleton();};int main(int argc , char *argv [])
{Singleton *singletonObj = Singleton ::GetInstance();cout<<singletonObj->GetTest()<<endl;singletonObj = Singleton ::GetInstance();cout<<singletonObj->GetTest()<<endl;
}

饿汉模式

#include <iostream>
using namespace std;class Singleton
{
public:static Singleton *GetInstance(){return m_instace;} 
private:Singleton();static Singleton *m_instance;};
Singleton* Singleton :: m_instance = new Singleton();
int main(int argc , char *argv [])
{Singleton *singletonObj = Singleton ::GetInstance();singletonObj = Singleton ::GetInstance();return 0;
}

http://www.ppmy.cn/news/1558524.html

相关文章

去除 el-input 输入框的边框(element-ui@2.15.13)

dgqdgqdeMac-mini spid-admin % yarn list --pattern element-ui yarn list v1.22.22 └─ element-ui2.15.13 ✨ Done in 0.23s.dgqdgqdeMac-mini spid-admin % yarn list vue yarn list v1.22.22 warning Filtering by arguments is deprecated. Please use the pattern opt…

Spring Boot 动态定时任务管理系统(轻量级实现)

Spring Boot项目中&#xff0c;实现动态增删和启停定时任务的功能对于许多应用场景来说至关重要。虽然Quartz框架是一个广泛使用的解决方案&#xff0c;但其复杂性和重量级特性可能使得项目变得臃肿和难以维护。为了解决这个问题&#xff0c;本项目旨在实现一个轻量级的定时任务…

imgproxy图像处理的高效与安全

摘要 imgproxy作为一个高效且安全的独立服务器,为图像处理提供了全新的解决方案。它不仅简化了图像调整和转换的过程,还极大地提升了处理速度,确保了整个流程的安全性。通过集成imgproxy,用户可以轻松优化网页上的图像,提高加载速度,改善用户体验。本文将深入探讨imgpro…

sed正则表达式元字符 和使用示例 sed变量替换示例

在使用 sed进行查找替换是我们也是可以使用正则表达式的&#xff0c; 不过sed默认只能使用基础正则表达式&#xff0c; ? 和 <> 和()分组等这些扩展正则表达式在sed里面默认是不能用的。 sed支持的正则元字符如下&#xff1a; 元字符 作用 * 前一个字符匹配0次或…

Blender高效优化工作流程快捷小功能插件 Haggis Tools V1.1.5

Haggis Tools V1.1.5 是一款专为Blender设计的插件&#xff0c;旨在优化工作流程、减少单调和重复的任务&#xff0c;从而为艺术家节省时间。这款插件适用于多个版本的Blender&#xff0c;能够有效提升工作效率。 Blender插件特点&#xff1a; 工作流程优化&#xff1a;专门设…

天池工业蒸汽量预测教程

一、引言 在现代工业生产中&#xff0c;蒸汽作为一种重要的能源载体&#xff0c;广泛应用于化工、制药、纺织、食品加工等众多领域。准确预测工业蒸汽量对于优化生产流程、提高能源利用效率、降低成本以及保障生产的稳定性具有至关重要的意义。天池举办的工业蒸汽量预测赛事&a…

穿山甲等广告联盟依据哪些维度给APP、小程序结算广告变现收益

媒体在开展广告变现商业化时&#xff0c;最关心的是变现收益问题&#xff0c;所运营的不同体量的APP、小程序能产生多少广告变现收益。#广告联盟# 广告变现的价格、收益不是一成不变的&#xff0c;广告转化是影响广告收益的重要因素之一。广告平台针对整个变现链路上的各环节&…

WebSocket | 背景 概念 原理 使用 优缺点及适用场景

1 背景 在 WebSocket 出现之前&#xff0c;为了实现推送技术&#xff0c;所用的技术都是轮询&#xff0c;轮询是指浏览器每隔一段时间向服务器发出 HTTP 请求&#xff0c;服务器再返回最新的数据给客户端 常见的轮询方式分为轮询与长轮询&#xff0c;它们的区别如下图所示&…