【设计模式】单例模式(线程安全)

 🔥博客主页: 我要成为C++领域大神
🎥系列专栏【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️

本博客致力于知识分享,与更多的人进行学习交流

什么是单例设计模式

单例模式是一种创建型设计模式, 它的核心思想是保证一个类只有一个实例,并提供一个全局访问点来访问这个实例。

  • 只有一个实例的意思是,在整个应用程序中,只存在该类的一个实例对象,而不是创建多个相同类型的对象。
  • 全局访问点的意思是,为了让其他类能够获取到这个唯一实例,该类提供了一个全局访问点(通常是一个静态方法),通过这个方法就能获得实例。

为什么要使用单例设计模式呢

简易来说,单例设计模式有以下几个优点让我们考虑使用它:

  • 全局控制:保证只有一个实例,这样就可以严格的控制客户怎样访问它以及何时访问它,简单的说就是对唯一实例的受控访问
  • 节省资源:也正是因为只有一个实例存在,就避免多次创建了相同的对象,从而节省了系统资源,而且多个模块还可以通过单例实例共享数据。
  • 懒加载:单例模式可以实现懒加载,只有在需要时才进行实例化,这无疑会提高程序的性能。

单例设计模式的基本要求

想要实现一个单例设计模式,必须遵循以下规则:

  • 私有的构造函数:防止外部代码直接创建类的实例
  • 私有的静态实例变量:保存该类的唯一实例
  • 公有的静态方法:通过公有的静态方法来获取类的实例

单例设计模式的实现

单例模式的实现方式有多种,包括懒汉式、饿汉式等。

饿汉式指的是在类加载时就已经完成了实例的创建,不管后面创建的实例有没有使用,先创建再说,所以叫做

“饿汉”。

而懒汉式指的是只有在请求实例时才会创建,如果在首次请求时还没有创建,就创建一个新的实例,如果已经创建,就返回已有的实例,意思就是需要使用了再创建,所以称为“懒汉”。

在多线程环境下,由于饿汉式在程序启动阶段就完成了实例的初始化,因此不存在多个线程同时尝试初始化实例的问题,但是懒汉式中多个线程同时访问 getInstance() 方法,并且在同一时刻检测到实例没有被创建,就可能会同时创建实例,从而导致多个实例被创建,这种情况下我们可以采用一些同步机制,例如使用互斥锁来确保在任何时刻只有一个线程能够执行实例的创建。

举个例子,你和小明都发现家里没米了,在你们没有相互通知的情况下,都会去超市买一袋米,这样就重复购买了,违背了单例模式。

饿汉式:

这里实现的是单例模式的一个模板类,方便进行复用。

另外,static是多线程安全的,不需要加锁

//模板类 单例模式 模板类封装了类的要点
template<typename T>
class HungrySingleton { //饿汉式的单例模式,是多线程安全的,不需要进行加锁双检测
public:
	static T* GetInstance() {
		static T instance;
		return &instance;
	}
protected:
	HungrySingleton(){}
	~HungrySingleton(){}
public:
	HungrySingleton(const HungrySingleton&) = delete;
	HungrySingleton(HungrySingleton&&) = delete;
	HungrySingleton operator = (const HungrySingleton&) = delete;
	HungrySingleton operator = (HungrySingleton&&) = delete;
};
class DesignPattern : public HungrySingleton<DesignPattern> {
	friend class HungrySingleton<DesignPattern>;
private:
	DesignPattern(){}
	~DesignPattern(){}
};

懒汉式:

懒汉式在实现时,需要考虑线程安全和指令重排问题

懒汉式1:可以实现自动释放资源,但是没有考虑线程安全问题

//懒汉式1:可以实现自动释放资源,但是没有考虑线程安全问题
class LazySingleton { 
public:
	LazySingleton* GetLazySingleton() {
		if (_instance == nullptr) {
			_instance = new LazySingleton;
			atexit(Destructor);
		}
		return _instance;
	}
private:
	static LazySingleton* _instance;
	LazySingleton(){}
	~LazySingleton(){}
	static void Destructor() {
		if (_instance != nullptr) {
			delete _instance;
			_instance = nullptr;
		}
	}
};
LazySingleton* LazySingleton::_instance = nullptr;

懒汉式2:双检测,但是没有考虑CPU的指令重排

单检测是指如果在获取单例判断空时,在最外面进行加锁,这样会让每次调用接口时都会加锁,浪费资源

所以可以将锁放到判空之后,new资源之前,但是在多线程情况下,两个线程同时到这里,会new两次,所以需要在上锁之后在进行一次判断是否为空

//可以在此处加互斥锁,但是每次获取单例时都会加互斥锁,浪费资源,new的时候再加互斥锁
		if (_instance == nullptr) {  //new的时候加互斥锁,但是需要双检测,因为在多核的线程上,两个线程同时执行到这里会new两次
			std::lock_guard<std::mutex> lock(_mutex); 
			if(_instance == nullptr) { //double check
				_instance = new LazySingleton2;  

指令重排具体体现在,new 操作符是可以分为三个部分的:

1. 分配内存 2. 调用构造函数 3. 返回对象指针

如果在多线程情况下,进行了指令重排,

线程A执行顺序为1 3 2,在它返回对象指针的同时,

线程B读取到了返回的对象指针,不为nullptr,直接就return了,拿到了一个没有调用构造函数的指针。造成了内存泄漏

//懒汉式2:双检测,但是没有考虑CPU的指令重排
class LazySingleton2 {
public:
	LazySingleton2* GetInstance() {
		//可以在此处加互斥锁,但是每次获取单例时都会加互斥锁,浪费资源,new的时候再加互斥锁
		if (_instance == nullptr) {  //new的时候加互斥锁,但是需要双检测,因为在多核的线程上,两个线程同时执行到这里会new两次
			std::lock_guard<std::mutex> lock(_mutex); 
			if(_instance == nullptr) { //double check
				_instance = new LazySingleton2;  
    ///new 操作符是可以分为三个部分的:如果在多线程情况下,进行了指令重排,
	/// 线程A执行顺序为1 3 2,在它返回对象指针的同时,
	/// 线程B读取到了返回的对象指针,不为nullptr,直接就return了,拿到了一个没有调用构造函数的指针。造成了内存泄漏
				//1. 分配内存
				//2. 调用构造函数
				//3. 返回对象指针
				atexit(Destructor);
			}
		}
		return _instance;
	}
private:
	static void Destructor() {
		if (_instance != nullptr) {
			delete _instance;
			_instance = nullptr;
		}
	}

	static LazySingleton2* _instance;
	static std::mutex _mutex;
	LazySingleton2(){}
	~LazySingleton2(){}
	LazySingleton2(const LazySingleton2&) = delete;
	LazySingleton2(LazySingleton2&&) = delete;
	LazySingleton2 operator = (const LazySingleton2 &) = delete;
	LazySingleton2 operator = (LazySingleton2&&) = delete;
};
LazySingleton2* LazySingleton2::_instance = nullptr;
std::mutex LazySingleton2::_mutex;

懒汉式3:最终版本 原子操作 + 互斥锁 + 内存屏障

内存屏障可以解决内存序的问题,指导CPU的指令重排,按什么样的规则进行重排

#include <atomic>
#include <mutex>

class LazySingleton3 {
public:
    static LazySingleton3* GetInstance() {
        // 尝试读取_instance,但不加内存序限制
        LazySingleton3* tmp = _instance.load(std::memory_order_relaxed);
        // 内存屏障,确保后续操作不会被重排到前面
        std::atomic_thread_fence(std::memory_order_acquire);
        if (tmp == nullptr) {
            std::lock_guard<std::mutex> lock(_mutex);
            tmp = _instance.load(std::memory_order_relaxed);
            if (tmp == nullptr) { // double check
                tmp = new LazySingleton3;
                std::atomic_thread_fence(std::memory_order_release); // 内存屏障,确保之前的操作完成后才进行后续操作
                _instance.store(tmp, std::memory_order_relaxed); // 最终将实例存储到_instance中
                atexit(Destructor);
            }
        }
        return tmp;
    }

private:
    static void Destructor() {
        if (_instance != nullptr) {
            delete _instance;
            _instance = nullptr;
        }
    }
    
    LazySingleton3() {}
    ~LazySingleton3() {}
    LazySingleton3(const LazySingleton3&) = delete;
    LazySingleton3(LazySingleton3&&) = delete;
    LazySingleton3& operator=(const LazySingleton3&) = delete;
    LazySingleton3& operator=(LazySingleton3&&) = delete;

    static std::atomic<LazySingleton3*> _instance;
    static std::mutex _mutex;
};

std::atomic<LazySingleton3*> LazySingleton3::_instance = nullptr;
std::mutex LazySingleton3::_mutex;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值