zoukankan      html  css  js  c++  java
  • 设计模式学习总结:(10)单例模式探讨

    单例模式(Singleton)很简单,从名字也很容易知道解决的是唯一对象创建问题,很多时候,如果因为一个对象只需要存在一份,正常对象创建方式有种杀鸡用牛刀的感觉。同时,也不能假设用户素质足够高,至少我们要保证从语法上,多个对象存在是不合理的,我们所要做的,就是约束使用者的行为。

    意图:

    保证一个类仅有一个实例,并提供一个全局访问点。


    在c++中为了限定对象的创建,我们需要把构造函数设置为私有,保证无法从外界构造,同时需要一个静态变量指针来保存唯一对象,最后至少还需要一个函数来获得这个唯一对象。

    class Singleton{
    private:
        Singleton()=default;
        
        static Singleton* _instance;
    public:
        static Singleton* getInstance();
        
    };
    
    Singleton* Singleton::m_instance=nullptr; //c++11 nullptr
    
    Singleton* Singleton::getInstance() {
        if (_instance == nullptr) {     //多线程触发点
            _instance = new Singleton();
        }
        return _instance;
    }

    李建忠老师的设计模式这里让我大开眼界,送上笔记一枚:

    -------------------------------------------------------------note------------------------

    这种实现方式在单线程下,已经很好了,但是再多线程下,存在安全隐患。

    在多线程情况下,如果多个线程同时执行到  if  判断那里,当第一个线程进入判断,然后第二个线程也进入判断,依次类推,可能有多个线程进入判断,这样就造成多个实例被new出来,而最终只有一个能被获得,剩下的将成为内存泄露的一分子。所以,我们很自然的想到用线程锁来解决,假设有这样一个线程锁,我们可以这样实现:

    Singleton* Singleton::getInstance() {
        Lock lock;
        
        if (_instance == nullptr) {
            _instance = new Singleton();
        }

    lock.relase();
    return _instance; }

    但是又考虑到,if判断,只有有限的一次可能执行到,剩下大超级大的一部分是不可能进入判断的,也就是说,仅仅为了有限的o(1)次,我们就每一次创建一个锁,然后释放,如果次数足够多,并发量足够大,效率有很大的影响。很自然是不允许的,对于很多需要效率的场景,需要有更好的做法,所以有了曾经风靡一时的双重锁解法。

    Singleton* Singleton::getInstance() {
        
        if(_instance==nullptr)
        {
            Lock lock;
            if (_instance == nullptr) 
            {
                _instance = new Singleton();
            }
            lock.realse();
        }
        return _instance;
    }

    保证了在足够多次的情况下,都不会获得锁,即使有幸获得锁,我们在进行判断,防止漏网之鱼,其实这代码从高级语言层面上看已经很完美了,然而,这里面竟然存在安全隐患。

    叫做内存reorder隐患。 参考资料:链接

    简单的说,_instance = new Singleton() 我们这一句的理想顺序是 创建空间->构造对象->赋值给指针。但是底层基于效率考虑,可能会编译成这样一种执行顺序,就是 创建空间->赋值给指针->构造对象。这样就造成如果刚好某个线程执行到了,赋值给指针,这一步,然后切换到另一个线程它判断,发现指针不是null,于是它就直接返回对象,注意,这个时候它返回了一个还未构造属性的对象。这就是问题所在。

    最后,附上c++11新标准的解决方案,直接复制代码过来。

    std::atomic<Singleton*> Singleton::_instance;
    std::mutex Singleton::m_mutex;
    
    Singleton* Singleton::getInstance() {
        Singleton* tmp = _instance.load(std::memory_order_relaxed);
        std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
        if (tmp == nullptr) {
            std::lock_guard<std::mutex> lock(m_mutex);
            tmp = _instance.load(std::memory_order_relaxed);
            if (tmp == nullptr) {
                tmp = new Singleton;
                std::atomic_thread_fence(std::memory_order_release);//释放内存fence
                m_instance.store(tmp, std::memory_order_relaxed);
            }
        }
        return tmp;
    }

    --------------------end----------------------------

    下面写一些见解,纯思考:

    ----------------------------------------------------------语法思考-------------------------------------------------------------------------------------

    c++类里面,static 可以定义成指针,也能定义成普通的成员。

    抛出疑问:

    class Singleton
    {
        Singleton()=default;
        static Singleton _instance;
    public:
        static Singleton getInstance();
    };
    Singleton Singleton::_instance = Singleton();
    Singleton Singleton::getInstance()
    {
        return _instance;
    }

    这种方式好像也能解决多线程问题,我不知道存不存在安全隐患,暂且保留,后续补充。

    另外,我们对比一下一个类里面如果这三种定义:

    class A
    {
    static A a; //合理
    static A *b; //合理
    
    A *c; //合理
    A d;//不合理
    }

    很正常的语法,之前也做过思考,我们只要知道第四个为什么不合理,一切都明了了。在类实例被创建的时候,如果是第四种形式,你可以想象,这样的定义是需要默认初始化,或者如果你给它一个类。但是他本身是一个A实例,这个A实例也存在这样的一个A实例,于是A实例里面有个A1实例,A1实例里面有个A2实例,何时是终结。就好像山上有个庙,庙里有个和尚,和尚说:“山上有个庙。。。”。

    如果是指针,那么可以默认初始化为null,这样null就不存在A实例,就不会进入和尚的庙。至于静态变量,如果你理解它的内存空间,其实不管是任何实例,都是同一个A实例,是唯一的同一个。基于此,我做过一个耐人寻味的测试:

    class A
    {
    static A a;
    public:
        int b;
    }
    A A::a = A();
    int main()
    {
        A test = A();
        test.a.a.a.a.a.b = 1;
        cout << test.a.b<<endl;
        test.a.a.a.a.a.a.a.a.a.a.a.b = 2;
        cout << test.a.b<<endl;
       return 0;
    }

    能猜到结果吗。

    ------------------------------------------------------------end--------------------------------------------------------------------------------------

  • 相关阅读:
    USB子系统gadget analyse
    关于嵌入式的学习和职业发展
    编译JNI动态库so
    Linux串口编程
    i2c_get_clientdata和to_i2c_client 的使用获取平台设备
    Kbuild 编译系统分析
    解决QPST 出现的 "服务器正在运行" 的 下载错误.
    Msm 高通平台配置记录之一
    USB Skeleton driver 分析记录
    网站应用数据库相关 过滤类
  • 原文地址:https://www.cnblogs.com/wuweixin/p/5452123.html
Copyright © 2011-2022 走看看