美文网首页
单例模式

单例模式

作者: 进击的Lancelot | 来源:发表于2020-08-07 19:40 被阅读0次

    定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式的最终目的是为了要实现对唯一实例受控访问。另外注意,由于子类对象中会包含有基类的子对象,因此使用单例模式的类不能够作为基类去派生其他的类。

    单例模式结构图:


    image.png

    根据对对象进行初始化时机的不同,单例模式可以分为懒汉式和饿汉式

    懒汉式

    懒汉式单例模式是指系统运行中,实例并不存在,只有当需要使用该实例时,才会去创建并使用实例。(这种方式要考虑线程安全)

    C++ 11 以前的标准

    双检查锁实现懒汉式单例模式(Double-checking Lock)

    class Singleton{
    public:
        static Singleton* getInstance(){
        // 先检查对象是否存在
        if(m_instance==nullptr){
            Lock lock; //伪代码
            if (m_instance == nullptr) {
                m_instance = new Singleton();
            }
        }
        return m_instance;
    }
    private:
        Singleton(); //私有构造函数,不允许使用者自己生成对象
        Singleton(const Singleton& other);
        static Singleton* m_instance; //静态成员变量 
    };
    

    在相当长的一段时间,迷惑了很多人,在2000年的时候才被人发现漏洞,而且在每种语言上都发现了。原因是内存读写的乱序执行(编译器的问题)。

    分析:m_instance = new Singleton()这句话可以分成三个步骤来执行:

    1. 分配了一个Singleton类型对象所需要的内存。

    2. 在分配的内存处构造Singleton类型的对象。

    3. 把分配的内存的地址赋给指针m_instance

    可能会认为这三个步骤是按顺序执行的,但实际上只能确定步骤1是最先执行的,步骤23却不一定。问题就出现在这。假如某个线程A在调用执行m_instance = new Singleton()的时候是按照1,3,2的顺序的,那么刚刚执行完步骤3Singleton类型分配了内存(此时m_instance就不是nullptr了)就切换到了线程B,由于m_instance已经不是nullptr了,所以线程B会直接执行return m_instance得到一个对象,而这个对象并没有真正的被构造!!严重bug就这么发生了。

    C++ 11中的版本

    C++11当中明确保证了 static local 对象是线程安全的,同时由于 static local object 是存放于静态区,因此不会出现内存泄漏的问题

    public:
        static Singleton& GetInstance(){
            static Singleton s;
            return s;
     }
    private:
        Singleton(){}
        ~Singleton(){}
        Singleton(Singleton const&) = delete;
        Singleton& operator=(Singleton const&) = delete;
    };
    

    饿汉式

    饿汉式是指系统一运行,就初始化创建实例,当需要时,直接调用即可。(本身就线程安全,没有多线程的问题)

    class Singleton
    {
    private:
        static Singleton instance;
        Singleton();
        ~Singleton();
    public:
        Singleton(const Singleton&) = delete;
        Singleton& operator=(const Singleton&) = delete;
        static Singleton& getInstance() {
            return instance;
        }
    }
    // initialize defaultly
    Singleton Singleton::instance;
    

    在饿汉式单例模式中,instance 是全局静态对象,因此初始化的过程将会在 main 之前的单线程启动阶段,因此不存在线程安全问题。

    补充内容:static 对象的初始化

    全局 static 对象(函数外)

    C++规定,non-local static 对象的初始化发生在main函数执行之前,也即main函数之前的单线程启动阶段,所以不存在线程安全问题。但C++没有规定多个non-local static 对象的初始化顺序,尤其是来自多个编译单元的non-local static对象,他们的初始化顺序是未定义的

    局部 static 对象(函数内)

    对于local static 对象,其初始化发生在控制流第一次执行到该对象的初始化语句时。多个线程的控制流可能同时到达其初始化语句。

    在C++11之前,在多线程环境下local static对象的初始化并不是线程安全的。具体表现就是:如果一个线程正在执行local static对象的初始化语句但还没有完成初始化,此时若其它线程也执行到该语句,那么这个线程会认为自己是第一次执行该语句并进入该local static对象的构造函数中。这会造成这个local static对象的重复构造,进而产生内存泄露问题。所以,local static对象在多线程环境下的重复构造问题是需要解决的。

    而C++11则在语言规范中解决了这个问题。C++11规定,在一个线程开始local static 对象的初始化后到完成初始化前,其他线程执行到这个local static对象的初始化语句就会等待,直到该local static 对象初始化完成。

    相关文章

      网友评论

          本文标题:单例模式

          本文链接:https://www.haomeiwen.com/subject/knjqdktx.html