zoukankan      html  css  js  c++  java
  • c++版本之单例模式

    单例模式(Singleton)是指一个类仅有一个实例对象,并且该类提供一个获得该实例对象的全局访问点,它包含三个关键元素:

    元素一:提供private类型的构造函数
    元素二:提供private类型的的静态成员变量,以保存唯一的实例对象;
    元素三:提供获得本类实例的全局访问点GetInstance函数,它是静态类型的

    初级版本

    根据这三个关键元素可以写出一个基本的单例模式,其代码如下:

    class CSinglton
    {
    private:
        //(1)私有额构造函数
        CSinglton(){}
        //在析构函数中释放实例对象
        ~CSinglton()
        {
            if (pInstance != NULL)
            {
                delete pInstance;
                pInstance = NULL;
            }
        }
    public:
        //(3)获得本类实例的唯一全局访问点
        static CSinglton* GetInstance()
        {
            //若实例不存在,则创建实例对象
            if (NULL == pInstance)
            {
                pInstance = new CSinglton();
            }
            //实例已经存在,直接该实例对象
            return pInstance;
        }
    
    private:
        static CSinglton* pInstance;//(2)唯一实例对象
    };
    
    //静态成员变量,类外初始化实例对象
    CSinglton* CSinglton::pInstance = NULL;

    由于CSinglton类的构造函数是私有的,外界无法创建实例对象,只能由类本身来创建;这里由GetInstance()全局访问点来创建唯一的实例对象;

    下面我们可以创建测试代码,判断是否符合预期,测试代码如下:

    CSinglton *pInstance1 = CSinglton::GetInstance();
    CSinglton *pInstance2 = CSinglton::GetInstance();
    
    if (pInstance1 == pInstance2)
    {
        cout << "同一个实例" << endl;
    } 
    else
    {
        cout << "是两个实例" << endl;
    }

    运行结果:

    这里写图片描述
    从测试结果可以看出,代码运行符合基本预期;

    多线程基础版本

    如果CSinglton 类运行在多线程环境下,可能会导致该类创建两个实例,并造成内存泄漏,原因分析如下:

    比如在线程A和线程B中均第一次调用GetInstance函数,当线程A开始执行new CSinglton()语句时,线程A暂停执行;而此刻线程B获得执行权利,并成功创建了实例对象B;当线程A又获得CPU时间片,则继续执行new CSinglton()语句创建实例对象A,导致实际中创建了两个实例对象,并且实例B的指针值被实例A覆盖,实例B没有被释放,导致内存泄漏;

    因此我们可以改进我们的代码,对GetInstance内部增加同步机制,修改代码如下:

    class CSinglton
    {
    private:
        //(1)私有额构造函数
        CSinglton(){}
        ~CSinglton()
        {
            if (pInstance != NULL)
            {
                delete pInstance;
                pInstance = NULL;
            }
        }
    public:
        //(3)获得本类实例的唯一全局访问点
        static CSinglton* GetInstance()
        {
            //利用MFC中的CSingleLock完成同步,只有一个线程会进入 
            CSingleLock singleLock(&m_CritSection);
            singleLock.Lock();
    
            if (NULL == pInstance)
            {
                //若实例不存在,则创建实例对象
                pInstance = new CSinglton();
            }
            singleLock.Unlock();
    
            return pInstance;
        }
    
    private:
        static CSinglton* pInstance;//(2)唯一实例对象
        static CCriticalSection m_CritSection;
    };
    
    //静态成员变量,类外初始化实例对象
    CSinglton* CSinglton::pInstance = NULL;
    CCriticalSection  CSinglton::m_CritSection;

    当一个线程已经位于临界区时,另一个线程将会被阻塞,直到当前线程退出临界区,另一个线程才会进入,就可以解决多线程同步问题;

    多线程高效版

    在一般的多线程环境下,以上代码就可以保证正确性,但是在高并发、访问量很大的环境下,上述的代码实现将对性能造成很大影响;因为每次调用GetInstance都需要加锁,解锁,比较浪费时间,解决方案是采用双重锁定。GetInstance多线程高效版代码如下:

    static CSinglton* GetInstance()
    {
         //仅在实例未被创建时加锁,其他时候直接返回
        if (NULL == pInstance)
        {
            CSingleLock singleLock(&m_CritSection);
            singleLock.Lock();
            if (NULL == pInstance)
            {
                //若实例不存在,则创建实例对象
                pInstance = new CSinglton();
            }
            singleLock.Unlock();
        }
    
        //实例已经存在,直接该实例对象
        return pInstance;
    }


    以上代码实现过程叫做“Double-Check-Locking(双重锁定)”,即我们不让线程每次都加锁,而仅在实例未被初始化时,才进行线程同步,不仅保证内存不泄漏,还大大提高访问性能;

    静态初始化版本

    这种版本不仅实现最简单而且还避免多线程下的不安全性,其代码如下:

    class CSinglton
    {
    private:
        CSinglton(){}
    
        ~CSinglton()
        {
            if (pInstance != NULL)
            {
                delete pInstance;
                pInstance = NULL;
            }
        }
    
    public:
        //获得本类实例的唯一全局访问点
        static CSinglton* GetInstance()
        {
            return pInstance;
        }
    private:
        static CSinglton* pInstance;
    };
    
    //程序运行初期就创建实例
    CSinglton* CSinglton::pInstance = new CSinglton;

    饿汉式和懒汉式单例类

    在单例模式中,常常提到了饿汉式单例类和懒汉式单例类两个词,上面提到的“初级版本”“多线程基础版本” “多线程高效版”都是懒汉式单例类,而“静态初始化版本”是饿汉式单例类,它们的区别主要如下:

    1. 饿汉式单例就是在程序运行之前就创建实例,不管最终程序中是否用到,都占用资源,形容饥不择食的模样;
    2. 懒汉式单例就是程序在第一次调用全局访问点时才实例对象;
    3. 饿汉式单例不需要考虑多线程的安全性,但是有可能导致资源浪费,懒汉式单例需要采用“双重锁定”才能保证系统性能和正确性;

    采用何种方式,我们应该根据实际应用情况区别对待;

    参考文章:

    http://blog.csdn.net/lovelion/article/details/17517213

  • 相关阅读:
    js 特殊字符处理
    sql server 查询新增
    idea 很多基础文件找不到
    js 千分位
    Navicat Premium 12新增标识列
    Javascript 树形菜单 (11个)
    Javascript调用后台方法
    Treeview绑定数据库
    Repeater实现GridView编辑修改模式
    如何用JS获取键盘上任意按键的值?兼容FireFox和IE js获取键盘ASCII码?js键盘事件全面控制
  • 原文地址:https://www.cnblogs.com/jinxiang1224/p/8468240.html
Copyright © 2011-2022 走看看