• 单例模式(内存栅栏)


    单例模式

    单例模式(Singleton Pattern),属于创建型模式,是最简单的设计模式之一。单例模式设计的类,在一个进程中只能产生一个实例。
    单例模式有以下特点:

    1. 单例类只有一个实例对象;
    2. 该单例对象必须由单例类自行创建;
    3. 单例类对外提供一个访问该单例的全局访问点;

    根据它的三个特点,可以写出以下代码:

    一个最简单的单例模式

    class Singleton
    {
    public:
        static Singleton* getInstance()
        {
            if (_instance == nullptr)
                _instance = new Singleton;
            return _instance;
        }
    
    private:
        static Singleton* _instance;
        Singleton(){ }
        ~Singleton(){ }
        Singleton(const Singleton&) = delete;
        Singleton(const Singleton&&) = delete;
    
    };
    Singleton* Singleton::_instance = nullptr;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    上面代码通过把构造和析构函数用private来修饰,并把左值拷贝和右值拷贝delete,让单例对象不能随便构造和析构,通过提供的静态方法getInstance可以获取全局唯一的实例对象。
    但在我们程序执行正常结束的过程中,单例对象的析构对象并没有被调用到。在我们的单例对象析构函数刷新某些资源,或者写日志记录对象状态之类必须在析构函数中做工作,这样的实现方式是不能满足的。
    因此,我们可以在构造这个全局的唯一实例时,注册一个atexit回调,在程序结束时调用实例的析构函数:

    程序结束会调用析构函数的单例

    #include 
    using namespace std;
    
    class Singleton
    {
    public:
        static Singleton* getInstance()
        {
            if (_instance == nullptr)
            {
                _instance = new Singleton;
                atexit(recovery);
            }
            return _instance;
        }
    
    private:
        static void recovery() { delete _instance; }
        static Singleton* _instance;
        Singleton(){ }
        ~Singleton() { cout << "~Singleton" << endl; }
        Singleton(const Singleton&) = delete;
        Singleton(const Singleton&&) = delete;
    
    };
    Singleton* Singleton::_instance = nullptr;
    
    int main()
    {
        Singleton::getInstance();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    多线程环境下的单例模式

    在多线程环境下,一个判空操作是无法保证我们单例模式的只有一个实例对象的特性的,很有可能会出现创建多个实例的情况,这时我们需要在创建实例时加上锁,一般采用锁加双重判断的方式实现:

    class Singleton
    {
    public:
        static Singleton* getInstance()
        {
            if (_instance == nullptr)
            {
                lock_guard<mutex> lock(_mutex);
                if (_instance == nullptr)
                {
                    _instance = new Singleton;
                    atexit(recovery); 
                }
            }
            return _instance;
        }
    
    private:
        static void recovery() { delete _instance; }
        static Singleton* _instance;
        static mutex _mutex;
        Singleton(){ }
        ~Singleton() { cout << "~Singleton" << endl; }
        Singleton(const Singleton&) = delete;
        Singleton(const Singleton&&) = delete;
    
    };
    Singleton* Singleton::_instance = nullptr;
    mutex Singleton::_mutex;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    防止指令重排的单例模式

    _instance = new Singleton;底层的执行需要经过这个三个步骤:

    1. 开辟内存空间
    2. 调用构造函数初始化
    3. 返回Singleton指针

    在执行这三条语句的时候,编译器、CPU为了优化可能会改变这三条语句的执行顺序进行重排,这样在执行完3,2还为执行时,另一个线程检测到_instance != nullptr,就会返回一个未初始化完成的一个对象,这样不安全,可以使用C++11提供的内存序来进行控制,包含在atomic头文件中
    在这里插入图片描述
    在这里插入图片描述

    #include 
    #include 
    #include 
    using namespace std;
    
    class Singleton
    {
    public:
        static Singleton* getInstance()
        {
            if (_instance.load(memory_order_acquire) == nullptr)//本线程后续所有的读操作都要在本条原子操作完成后执行			
            {															
                lock_guard<mutex> lock(_mutex);							
                if (_instance.load(memory_order_relaxed) == nullptr)//内部
                {
                    // 1. 开辟内存空间                         
                    // 2. 调用构造函数初始化
                    _instance.store(new Singleton, memory_order_release);//本线程之前所有的写操作完成后才执行本条原子操作 3. 返回Singleton指针
                    atexit(recovery); 
                }
            }
            return _instance.load(memory_order_relaxed);    
        }
    
    private:
        static void recovery() { delete _instance; }
        static atomic<Singleton*> _instance;
        static mutex _mutex;
        Singleton(){ }
        ~Singleton() { cout << "~Singleton" << endl; }
        Singleton(const Singleton&) = delete;
        Singleton(const Singleton&&) = delete;
    
    };
    atomic<Singleton*> Singleton::_instance;
    mutex Singleton::_mutex;
    
    int main()
    {
        cout << Singleton::getInstance() << endl;
        cout << Singleton::getInstance() << endl;
        Singleton::getInstance();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    精简安全高效的单例模式

    定义静态局部对象,只有运行到了才会加载,由系统来提供静态成员线程安全行的保证,并会在程序结束时调用其析构函数。

    #include 
    using namespace std;
    
    class Singleton
    {
    public:
        static Singleton* getInstance()
        {
            static Singleton instance;
            return &instance;
        }
    
    private:
        static Singleton* _instance;
        Singleton() { }
        ~Singleton() { cout << "~Singleton" << endl; }
        Singleton(const Singleton&) = delete;
        Singleton(const Singleton&&) = delete;
    
    };
    
    int main()
    {
        cout << Singleton::getInstance() << endl;
        cout << Singleton::getInstance() << endl;
        Singleton::getInstance();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
  • 相关阅读:
    渗透测试——2
    沉睡者IT - 做网赚就是搞流量搞转化!
    常用的消息协议
    iptables详解:链、表、表链关系、规则的基本使用
    Codeforces 1281F 树上背包
    @Component与@Configuration区别
    通过Redis实现一个异步请求-响应程序
    MySQL 与 PostgreSQL的区别
    Android 沉浸式状态栏
    重学 JavaSE 高阶
  • 原文地址:https://blog.csdn.net/weixin_43973403/article/details/126458176