• 【设计模式之单例模式二】



    在这里插入图片描述

    单例模式:要求类只有一个对象实例,数据库连接池、线程池以及Windows任务管理器都是单例模式的应用。

    那么如何去实现一个单例模式呢?C++中有5种实现的方式,首先从最简单说起

    1. 单线程实现

    常规写法

    class Singleton{
    public:
    	static Singleton* getInstance()
    	{
    		if(_instance == nullptr)
    			_instance = new Singleton();
    		return _instance;
    	}
    private:
    	Singleton() = default;
    	Singleton* _instance;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    单线程种此写法没有任何问题,但是不适用多线程环境。 假如有两个线程A和线程B:
    线程A先获得执行权,执行Singleton的getInstance获取实例_instance。发现_instance == nullptr,
    则线程A开始准备执行new Singleton()操作。该操作细分为三步:

    • 1、Singleton的对象开辟内存空间;
    • 2、在开辟的内存空间中构造对象;
    • 3、完成构造之后用_instance指针指向内存空间。

    但是在多线程环境中,A线程可能会在即将执行这三布之前被挂起,线程B获得cpu的执行权,这就导致 线程B中_instance仍旧为nullptr,B执行new Singleton()操作。B执行完成后CPU执行权返回给A,A继续执行,A会继续执行new Singleton()

    2. 多线程实现

    #include
    using namespace std;
    mutex mt;
    class Singleton {
    private:
    	static Singleton* _instance;
    	Singleton() {}
    public:
    	static Singleton* getInstance() {
    		mt.lock(); // 加锁
    		if (_instance == nullptr)
    			_instance = new Singleton();
    		mt.unlock(); // 解锁
    		return _instance;
    	}
    };
    Singleton* Singleton::_instance = nullptr;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    虽然加锁可以解决多线程下多次创建_instance的问题,但是每执行getInstance函数都需要进行加锁判断,消耗资源

    3. 双检查锁

    #include
    using namespace std;
    mutex mt;
    class Singleton {
    private:
    	static Singleton* _instance;
    	Singleton() {}
    public:
    	static Singleton* getInstance() {
    		if (_instance == nullptr) {
    			mt.lock(); // 解锁
    			if (_instance == nullptr)
    				_instance = new Singleton();
    			mt.unlock(); // 加锁
    		}
    		return _instance;
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    如果实例已经创建,就不需要同步。相反开始同步线程 但还是有问题,如_instance = new Singleton();
    这句的执行同样有三步操作

    • 1、为Singleton对象开辟空间
    • 2、在分配的空间中构造Singleton的对象
    • 3、将_instance指向分配的空间

    在实际编译器执行中,这三步并不是严格按照1、2、3顺序执行,而是可能 发生其中某一步先执行的情况。 如执行顺序为:1->3->2,这样就造成线程不安全 如: 线程A执行实例化操作,但是在执行完1、2步骤的时候意外挂起,此时线程B开始执行。由于_instance指向了一块空间区域,所以其不为空,则线程B不执行实例话操作,而
    _instance执行的内存区域并没有对象!

    4. C++11版本简介跨平台方案(推荐版本)

    class Singleton {
    public:
    	static Singleton& getInstance() {
    		static Singleton value;
    		return value;
    	}
    private:
    	Singleton() = default;
    	Singleton(const Singleton& other) = delete;
    	Singleton& operator = (const Singleton&) = delete;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    引用C++11的特性和局部static只初始化一次的特性。但是需要禁用拷贝构造函数和拷贝赋值运算

    5. 使用C++11提供的call_once

    #include
    #include
    once_flag flag;
    class Singleton {
    public:
    	static Singleton& getInstance() {
    		call_once(flag, []() {_instance.reset(new Singleton())});
    		return *_instance;
    	}
    private:
    	static unique_ptr<Singleton> _instance;
    	Singleton() = default;
    	Singleton(const Singleton& other) = delete;
    	Singleton& operator = (const Singleton&) = delete;
    };
    unique_ptr<Singleton> Singleton::_instance;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
  • 相关阅读:
    PlantUML使用入门
    三台linux服务器部署ceph集群
    实习日报-2022-7-29
    设计模式学习(十九):访问者模式
    java健身房管理系统设计计算机毕业设计MyBatis+系统+LW文档+源码+调试部署
    ES集群中节点与分片的区别
    数据结构学习笔记——图的存储结构(邻接矩阵和邻接表)
    Cesium关于Entity中的parent、isShowing、entityCollection和监听事件的探讨
    温故而知新十(C++)
    数据结构与算法 #18 下跳棋,极富想象力的同向双指针模拟
  • 原文地址:https://blog.csdn.net/weixin_42483745/article/details/126757327