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

文章目录

      • 单例模式(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;
}


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ArKTS基础组件3

一.PatternLock 图案密码锁组件&#xff0c;以九宫格图案的方式输入密码&#xff0c;用于密码验证场景 属性: sideLength:设置组件的宽度和高度&#xff08;宽高相同&#xff09;。设置为0或负数时组件不显示。 参数名类型必填说明valueLength是组件的宽度和高度。默认值&a…

python2:数据、运算符与表达式

一&#xff0c;数据类型&#xff1a; 数据类型是计算机对现实中数据的抽象&#xff0c;不同的数据类型其存储格式、数据范围、 计算要求都各不相同。 Python中的数据类型可以分为以下三类 基础类型&#xff1a;字符串(str)、整数(int)、实数(float)、布尔(bool)、复数(compl…

tortoisegit推送失败

tortoisegit推送失败 git.exe push --progress -- "origin" testLidar:testLidar /usr/bin/bash: gitgithub.com: No such file or directory fatal: Could not read from remote repository. Please make sure you have the correct access rights and the reposit…

pyinstaller打包资源文件和ini配置文件怎么放

1.如果出现无法成功完成操作&#xff0c;因为文件包含病毒或潜在的垃圾软件&#xff0c;说明你的版本太高&#xff0c;更换pyinstaller版本。 pip install pyinstaller6.2.02.一开始打包的时windows下尽量选择打成文件夹的并且要是带命令行窗口的&#xff0c;容易查看错误。 …

autMan奥特曼机器人-autMan的PHP环境

直装版请自行安装php环境。 docker版本预置了php环境&#xff0c;如下图&#xff1a; 如果使用插件"test php"测试环境时&#xff0c;实时日志有报错如下&#xff1a; 可进入终端&#xff0c;输入两条命令 apk add curl apk add php-curl

uniApp打包H5发布到服务器(docker)

使用docker部署uniApp打包后的H5项目记录&#xff0c;好像和VUE项目打包没什么区别... 用HX打开项目&#xff0c;首先调整manifest.json文件 开始用HX打包 填服务器域名和端口号~ 打包完成后可以看到控制台信息 我们可以在web文件夹下拿到下面打包好的静态文件 用FinalShell或…

【Leetcode】1705. 吃苹果的最大数目

文章目录 题目思路代码复杂度分析时间复杂度空间复杂度 结果总结 题目 题目链接&#x1f517; 有一棵特殊的苹果树&#xff0c;一连 n n n 天&#xff0c;每天都可以长出若干个苹果。在第 i i i 天&#xff0c;树上会长出 a p p l e s [ i ] apples[i] apples[i] 个苹果&a…

4、数据结构与算法解析(C语言版)--栈

栈的数据存储遵循“后进先出的规则”&#xff0c;这在计算机里面是非常有用的&#xff0c;比如word等编辑软件的"撤销"功能&#xff0c;就是使用栈进行实现的。 1、创建项目 main.h #ifndef _MAIN_H #define _MAIN_H#include <stdio.h> #include <stdlib.…

施耐德变频器ATV320系列技术优势:创新与安全并重

在工业自动化领域&#xff0c;追求高效、安全与智能已成为不可阻挡的趋势。施耐德变频器ATV320系列凭借其强大的设计标准和全球认证&#xff0c;成为能够帮助企业降低安装成本&#xff0c;提高设备性能的创新解决方案。 【全球认证&#xff0c;品质保障】ATV320 系列秉持施耐德…

【软考高级】系统架构设计师复习笔记-精华版

文章目录 前言0 系统架构设计师0.1 考架构还是考系分0.2 架构核心知识0.3 架构教材变化 1 计算机操作系统1.1 cpu 组成1.2 内核的五大功能1.3 流水线技术1.4 段页式存储1.5 I/O 软件1.6 文件管理1.7 系统工程相关 2 嵌入式2.1 嵌入式技术2.2 板级支持包&#xff08;BSP&#xf…

并发编程(19)——引用计数型无锁栈

文章目录 十九、day191. 引用计数2. 代码实现2.1 单引用计数器无锁栈2.2 双引用计数器无锁栈 3. 本节的一些理解 十九、day19 上一节我们学习通过侯删链表以及风险指针与侯删链表的组合两种方式实现了并发无锁栈&#xff0c;但是这两种方式有以下缺点&#xff1a; 第一种方式…

大恒相机开发(2)—Python软触发调用采集图像

大恒相机开发&#xff08;2&#xff09;—Python软触发调用采集图像 完整代码详细解读和功能说明扩展学习 这段代码是一个Python程序&#xff0c;用于从大恒相机采集图像&#xff0c;通过软件触发来采集图像。 完整代码 咱们直接上python的完整代码&#xff1a; # version:…

步进电机直线插补

基础原理 代码部分

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

第一章&#xff1a;数据结构导论 无重要考点&#xff0c;仅需了解时间复杂度。 第二章&#xff1a;线性表 1.获得线性表第i个元素 void GetElem_sq(SqList L, int i, ElemType &e) {if (i<1 || i>L.length) ErrorMsg("Invalid i value"); //注意错误监…

Windows11 安装 Ubuntu-20.04,同时安装配置 zsh shell,配置 git 别名(alias),大大提高开发效率

背景&#xff1a;家里配置了一台 Windows 电脑&#xff0c;有时候需要用到 vscode 开发测试一些代码&#xff0c;在使用过程中发现原生 windows 敲代码不是很友好&#xff0c;于是想到配置 wsl&#xff0c;安装 Ubuntu&#xff0c;并安装配置 zsh shell&#xff0c;同时配置 gi…

PE文件结构

PE文件是Windows系统下可执行文件的总称&#xff0c;英文全称 Portable Executable 可移植的可执行文件&#xff0c;常见的有exe、dll、sys、com、ocx 对于学习反&#xff08;木马、免杀、病毒、外挂、内核&#xff09;&#xff0c;了解PE文件结构是非常有必要且非常非常重要的…

Helm 官方脚本

Helm 官方脚本 #!/usr/bin/env bash# Copyright The Helm Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # …

JSON 系列之1:将 JSON 数据存储在 Oracle 数据库中

本文为Oracle数据库JSON学习系列的第一篇&#xff0c;讲述如何将JSON文档存储到数据库中&#xff0c;包括了版本为19c和23ai的情形。 19c中的JSON 先来看一下数据库版本为19c时的情形。 创建表colortab&#xff0c;其中color列的长度设为4000。若color的长度需要设为32767&a…

C语言-结构体内存大小

#include <stdio.h> #include <string.h> struct S1 { char a;//1 int b;//4 char c;//1 }; //分析 默认对齐数 成员对齐数 对齐数(前两个最小值) 最大对齐数 // 8 1 …

PyTorch 神经网络回归(Regression)任务:关系拟合与优化过程

PyTorch 神经网络回归&#xff08;Regression&#xff09;任务&#xff1a;关系拟合与优化过程 本教程介绍了如何使用 PyTorch 构建一个简单的神经网络来实现关系拟合&#xff0c;具体演示了从数据准备到模型训练和可视化的完整过程。首先&#xff0c;利用一维线性空间生成带噪…