懒汉式和饿汉式单例模式实现

目录

饿汉式单例模式

懒汉式单例模式:

版本1:加锁机制的线程安全的懒汉单例模式

版本2:不加锁实现的线程安全的懒汉单例模式


单例模式:一个类不管创建多少次对象,永远只能得到该类型一个对象的实例;

常用的比如日志模块线程池模块:我们用类提供了一些方法封装了一个日志模块,软件可能有很多个功能模块,这些模块都要通过日志模块写一些软件运行过程中的日志信息到磁盘上,所以应该把这些信息给一个日志模块的对象,信息如何处理成日志都由一个日志对象来处理;单例模式分为饿汉式单例模式懒汉式单例模式

饿汉式单例模式

        还没有通过单例模式类提供的接口函数去获取实例对象,这个实例对象早在程序启动阶段就已经产生了;由于已经在类的外部进行了静态成员的定义,所以即使没有调用生成实例的函数, 这个对象就已经存在了;【饿汉式一定是线程安全的,因为饿汉式的单例对象是静态成员变量,静态成员变量 在数据段,程序启动的时候就已经创建好了;如果程序运行过程中没有用到单例对象,那么提前进行初始化就白做了; 而且如果程序中太多这样的饿汉式单例类存在,那么程序启动时候要花费很多时间去初始化这些单例对象,那么 程序的启动时间就会变得很长;】

//1.【饿汉式单例模式】:
class Singleton { 
public:
    static Singleton* getInstance() { //3.获取类的唯一实例对象的函数,静态函数
        return &instance;
    }

private:
/*所以要限制对象创建个数,首先要限制构造函数的访问,所以我们先将构造函数私有化,
这样外部就不能调用构造函数了,因此也就无法直接定义对象了;同时删除左值引用的拷贝
构造函数和拷贝赋值运算符;*/
    Singleton() { //#1.构造函数私有化}
    Singleton(const Singleton&) = delete; 
    Singleton& operator =(const Singleton&) = delete; 
    //4.删除单例模式类的左值引用的拷贝构造函数和拷贝赋值运算符
    static Singleton instance;//2.类的静态成员声明,即类的一个唯一的实例对象
};
Singleton Singleton::instance; //类中的静态成员是声明,需要在类外定义

懒汉式单例模式:

唯一的实例对象直到第一次调用单例模式类的接口函数获取实例对象才进行初始化;

//注:下面代码错误,不是线程安全的懒汉式单例模式,需要改进
class Singleton {
private:
    Singleton(){} //1.构造函数私有化
    static Singleton* instance; //2.实例定义为指针类型
    Singleton(const Singleton&) = delete; //3.删除类的左值引用的拷贝构造函数和拷贝赋值运算符
    Singleton& operator = (const Singleton&) = delete;
public:
    static Singleton* getInstance() { //4.获取类的唯一实例的接口函数
        if (instance == nullptr) {
            return new Singleton();
        }
        return instance;
    }
    //注:这不是线程安全的单例模式,因为getInstance函数不是可重入的函数;
 //可重入就是这个函数还没执行完,能否再被调用一次?
 //如果一个函数可以再多线程环境下运行,不会发生静态条件,那么这个函数就是可重入函数; 
};
Singleton* Singleton::instance = nullptr;

版本1:加锁机制的线程安全的懒汉单例模式

std::mutex mtx;//全局互斥锁
class Singleton {
private:
    Singleton() {} //1.构造函数私有化
    static Singleton* volatile instance; //2.实例定义为static 指针类型且被volatile修饰
    /*注意:要给指针加上volatile ,因为instance是静态变量,存在在数据段,这是属于同一个进程多个线程共享的内存,
    而线程为了提高效率,CPU在执行线程指令的时候,为了加快指令的执行,会让线程把共享内存里面的变量都拷贝一份放到线程缓存里面,
    所以需要对instance指针加上volatile,即:给共享变量加上volatile关键字,让线程不再进行缓存,而是直接访问原始内存的共享变量;
    这样一个线程对共享变量的更改就会马上反映到另外一个线程了;
    会将 */
    Singleton(const Singleton&) = delete; //3.删除类的左值引用的拷贝构造函数和拷贝赋值运算符
    Singleton& operator = (const Singleton&) = delete;
public:
    static Singleton* getInstance() { //4.获取类的唯一实例的接口函数
        if (instance == nullptr) {
            std::lock_guard<std::mutex> guard(mtx); //这样只有第一次生成对象才进行加锁,后面再获取实例就不会进来了
            if (instance == nullptr) {
                //锁+双重判断保证多线程下运行正确(锁加在里面需要进来双重判断,因为会有多个线程进来,instance可能会被构造多次)
                instance = new Singleton();
                //这里new会有三件事情发生:1.new底层调用operator new 开辟内存 2.调用构造函数初始化对象 3.给instance赋值
                //【这里就是一块临界区代码段,所以需要进行互斥锁;】
            }
        }
        return instance;
    }

};
Singleton* volatile Singleton::instance = nullptr; //类中声明的静态成员变量必须在类外定义;
//注意volatile要在类中声明和类外定义都要加; 而static只在类中声明加,类外定义不能加;

版本2:不加锁实现的线程安全的懒汉单例模式

版本2:不加锁实现的线程安全的懒汉单例模式
因为instance是函数中静态的局部变量,所以他的内存在程序启动阶段就会分配内存,但它的初始化在第一次运行时候;
所以如果没有调用getInstance函数,编译器不会调用构造函数去初始化这个静态局部对象;
只有当第一次调用getInstance函数才会调用构造函数去初始化这个局部静态对象;
并且它是线程安全的,<因为函数静态局部变量的初始化在汇编指令上自动添加线程互斥指令了>

class Singleton {
public:
    static Singleton* getInstance() { //3.获取类的唯一实例对象的接口函数
        static Singleton instance; //定义类的唯一实例对象:函数静态的局部变量
        return &instance;
    }
private:
    Singleton() { //对单例的很多初始化代码.......
    } //#1.构造函数私有化
    Singleton(const Singleton&) = delete; //2.删除带左值引用的拷贝构造函数和拷贝赋值运算符
    Singleton& operator = (const Singleton&) = delete;
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值