【单例模式】深入理解懒汉与饿汉模式

文章目录

前言

本篇文章我们主要来剖析程序设计当中比较常用的一个设计模式------单例模式,我将带领大家从单例模式的定义开始学习,然后介绍两种实现单例的方法:懒汉模式和饿汉模式,了解了原理后我将用一段简单的C++代码让大家体会单例模式

单例模式的介绍

简介

单例模式是一种创建型的设计模式,它的核心思想是:保证一个类在整个程序运行过程当中只实例化出一个对象,并提供一个全局访问接口来访问类中定义的方法

为什么要有单例模式

为了解决这个问题,我们首先来做出假设:如果我们不使用单例会发生什么?

首先,如果没有单例的约束,那么在一个项目当中我们可以实例化出多个对象,对于那些只需要创建一个对象的业务逻辑而言是不安全的,比如:全局资源管理器、日志系统、缓存、系统上下文等等这些场景,如果在这些场景里面我们不对对象进行约束,那么就会造成这样的一个问题:例如对于一个日志而言,如果我们创建了两个对象,这样极易造成状态不一致的问题;而对于缓存系统而言,如果我们创建了多个对象,就会导致资源被重复创建,从而造成内存资源的浪费;因此对于上述种种问题而言,我们必须限制用户有且只能创建一个对象

其次,我们在实际项目当中为了提高程序的性能,往往会加入多线程,如果我们允许一个类实例化出多个对象,那么在多线程状态下就会造成线程安全的问题,而我们引入单例模式就可以保证多个线程在并发状态下都是对同一个对象进行操作,这样就可以保证线程安全

最后,在实际工作当中,会出现这样的问题,同一份代码,一开始能跑,后来为了完善一些问题,我们需要在这份代码里面加入各种if else等语句,写着写着就会忘记我们最开始操作的对象,如果我们引入单例模式就会在根本上杜绝这样的问题,只需要提供一个统一的访问接口操作这个对象

综上所述:如果不用单例,系统中会出现多个语义上本应唯一的对象,从而导致状态不一致、资源冲突、生命周期混乱和并发问题,使系统行为不可预测。

应用场景

通过上面的探讨,我们此时就可以知道在什么情况下适合使用单例:

  • 全局的资源配置管理
  • 日志系统
  • 缓存与对象池

如何实现

实现单例模式有以下几个难点:

  • 首先就是如何保证一个类只能创建一个对象
  • 其次是我们该如何做到每次只对一个对象进行操作

为了解决上面这两个难点,我们首先来思考一下对象是如何被创建出来的?

通常情况下,一个对象想要被创建出来要经过以下步骤:内存分配空间、初始化、用一个指针/引用指向它,这一流程对于绝大多数编程语言都是适用的

那么为了保证我们只创建一个对象,我们必须要保证每次操作的内存空间都是同一份,在C++中我们可以使用引用轻松完成这个任务,但是引用只能保证我们每次操作的空间是同一份,但是我们每次操作的对象却并不是同一份,此时我们的问题就变成了该如何保证每一次操作的都是同一个对象?

此时我们再思考一个问题:为什么我们能创建出不同的对象?

从C++的语法上看,我们之所以能够通过同一个类创建出不同的对象,是因为我们每次都调用了构造函数,构造函数底层帮我们在栈上开辟了一块空间

那么为了解决这个问题,我们需要让构造函数只被调用一次,那么如何才能让构造函数只被调用一次呢?我们可以把构造函数私有化,把构造函数变成私有的成员函数,那么一个对象没有了默认构造函数,我们就没有办法创建出对象,为了解决这个问题,我们必须提供一个可以被外部直接调用的静态成员函数,然后在这个静态成员函数里面初始化对象,由此我们便有了实现单例模式的思路

步骤:

  • 将构造函数私有化
  • 提供一个全局的静态成员函数
  • 通过这个静态成员函数去访问这个类的接口

单例模式分类

我们从单例模式的实现角度,可以把单例模式分为两种:懒汉模式和饿汉模式,下面我将通过代码带领大家一起学习懒汉模式和饿汉模式,从代码上体会它们的区别

饿汉模式

所谓的饿汉顾名思义就是在吃饭的时候等不及想要吃饭,这句话翻译成代码语言就是:在程序启动时我们就创建好了对象,只要用户调用,那么我们就可以立刻访问到系统资源,根据这个原理,我们可以写下下面的代码:

cpp 复制代码
class EagerSingleton
{
public:
    static EagerSingleton&GetInstance()
    {
        return _instance;
    }
    void Print()
    {
        std::cout<<"单例饿汉测试"<<std::endl;
    }
private:
    EagerSingleton()
    {
        std::cout<<"这是单例饿汉模式"<<std::endl;
    }
    ~EagerSingleton()
    {
        std::cout<<"饿汉模式析构完成"<<std::endl;
    }
    EagerSingleton(const EagerSingleton&)
    {
        std::cout<<"这是饿汉模式的拷贝构造函数"<<std::endl;
    }
    EagerSingleton&operator=(const EagerSingleton&)
    {
        std::cout<<"这是饿汉模式的赋值拷贝函数"<<std::endl;
        return *this;
    }
    static EagerSingleton _instance;
};

EagerSingleton EagerSingleton::_instance;

我们来分析上面的代码:

  • 首先,我们私有化了构造、拷贝、赋值、析构函数,这样用户就无法随时随地创建对象
  • 其次,我们创建了一个静态的成员变量,这个成员变量就是类本身,由于是静态的成员变量,所以我们要遵循类内申明,类外定义的原则,此时我们就创建了这个对象
  • 最后,我们提供了一个静态成员函数,这个静态成员函数返回静态成员变量,通过这个函数来访问类中的函数接口

懒汉模式

懒汉模式顾名思义就是,吃完饭懒得去洗碗,只有在下次吃饭的时候才去洗碗,翻译成程序语言就是:刚开始时不创建对象,只有在使用的时候才创建对象,代码如下所示:

cpp 复制代码
//懒汉模式:使用时才创建单例对象
class SingletonLazy
{
public:
    static SingletonLazy&GetInstance()
    {
        static SingletonLazy _instance;
        return _instance;
    }
    void Print()
    {
        std::cout<<"单例懒汉测试"<<std::endl;
    }
private:
    SingletonLazy()
    {
        std::cout<<"这是单例懒汉模式"<<std::endl;
    }
    ~SingletonLazy()
    {
        std::cout<<"懒汉模式析构完成"<<std::endl;
    }
    SingletonLazy&operator=(const SingletonLazy&)
    {
        std::cout<<"这是懒汉模式的赋值拷贝函数"<<std::endl;
        return *this;
    }
    SingletonLazy(const SingletonLazy&)
    {
        std::cout<<"这是懒汉模式的拷贝构造函数"<<std::endl;
    }
};

小结

以上就是本篇文章的主要内容,受限于博主的知识水平可能文章当中会存在错误,欢迎大家指正,最后如果觉得我的文章对您有所帮助的话希望能够点赞、关注加收藏,您的支持就是我创作的最大动力

相关推荐
better_liang2 小时前
每日Java面试场景题知识点之-ThreadLocal在Web项目中的实战应用
java· threadlocal· web开发· 多线程· 企业级开发
用户6802659051192 小时前
2026年企业级网络监控选型指南
javascript·后端·面试
Rysxt_2 小时前
Spring Boot 4.0 新特性深度解析与实战教程
java·spring boot·后端
WX-bisheyuange2 小时前
基于Spring Boot的社团管理系统的设计与实现
前端·javascript·vue.js·毕业设计
Wang15302 小时前
Java集合框架
java
梦想的旅途22 小时前
企业微信外部群消息推送实战指南
java·golang·企业微信
橙某人2 小时前
LogicFlow 插件魔改实录:手把手教你重写动态分组(DynamicGroup)🛠️
前端·javascript·vue.js
独自破碎E3 小时前
怎么实现一个滑动验证码功能?又如何防止被机器识别破解
java·spring boot·后端
2501_944446003 小时前
Flutter&OpenHarmony状态管理方案详解
开发语言·javascript·flutter