• 第3章 创建型模式—单例模式


    1. 单例模式(Singleton Pattern)

    (1)定义:保证一个类仅有一个实例,同时提供能对该实例加以访问的全局访问方法。

     

    (2)解决思路:

      ①在类中,要构造一个实例,就必须调用类的构造函数。如此,为了防止在外部调用类的构造函数而创建实例,需要将构造函数的访问权限设为protected或private;

      ②最后,需要提供全局访问点,就需要在类中定义一个static函数,返回在类内部唯一构造的实例。

    2. 单例模式的实现

    (1)懒汉式:

      ①其特点是延迟加载,典型的以“时间换空间”做法。即只会在全局访问方法首次被调用时才被创建

      ②问题:new出来的实例不能自动释放,可能存在内存泄漏问题。其解决方法有2种。

        A.方法1:在单例类内部定义专有的嵌套类,并在单例类定义一个私有的专门用于释放的静态成员,当程序结束析构全局变量时,会自动调用并释放这个单例。

        B.方法2:在单例类提供一个专门用于释放的静态成员函数,并在需要时手动释放。

      ③在实际项目中,单例一般是个全局变量,它的生命期随软件运行结束而自然终止。也就没有内存泄漏,但是如果单例中用到了一个文件锁、文件句柄或数据库连接,这些并不会随程序的关闭而立即关闭,必须在程序关闭前进行手动释放。

    //懒汉式1

    //创建型模式:单例模式
    
    #include <stdio.h>
    
    //懒汉式1:缺点,须手工释放
    class CSingleton
    {
    public:
        //提供全局访问的访问点
        static CSingleton* getInstance()
        {
            if(m_pInstance == NULL)
            {
                m_pInstance = new CSingleton();
            }
            
            return m_pInstance;
        }
        
        //清除:须手工调用
        static void clearInstance()
        {
            if(m_pInstance != NULL)
            {
                delete m_pInstance;
                m_pInstance = NULL;
            }
        }
        
    private:
        //将构造函数设为私有属性
        CSingleton(){};
        
        //把复制构造函数和=操作符也设为私有,防止被复制
        CSingleton(const CSingleton&){}
        CSingleton& operator=(const CSingleton&){}
        
        static CSingleton* m_pInstance;   
    };
    
    CSingleton* CSingleton::m_pInstance = NULL; 
    
    int main()
    {
        CSingleton* s1 = CSingleton::getInstance();
        CSingleton* s2 = CSingleton::getInstance();
        
        printf("s1 = %p
    ", s1);
        printf("s2 = %p
    ", s2);
        
        CSingleton::destroyInstance();
        
        return 0;
    }

    //懒汉式2

    //创建型模式:单例模式
    
    #include <stdio.h>
    
    //懒汉式2:可自动回收垃圾
    class CSingleton
    {
    public:
        //提供全局访问的访问点
        static CSingleton* getInstance()
        {
            if(m_pInstance == NULL)
            {
                m_pInstance = new CSingleton();
            }
            
            return m_pInstance;
        }
       
    private:
        //将构造函数设为私有属性
        CSingleton(){};
        static CSingleton* m_pInstance; 
        
        //内部类,用于垃圾回收
        class GC
        {
        public:
            ~GC()
            {
                //在这里销毁资源,比如数据库连接,句柄等。
                if(m_pInstance != NULL)
                {
                    delete m_pInstance;
                    m_pInstance = NULL;
                    printf("test: ~GC
    ");
                }
            }
        };
        
        static GC gc;
        
    };
    
    //静态成员变量的初始化
    CSingleton* CSingleton::m_pInstance = NULL;
    
    //全局静态变量,会被自动销毁,从而实现对单例的垃圾回收
    CSingleton::GC CSingleton::gc; 
    
    int main()
    {
        CSingleton* s1 = CSingleton::getInstance();
        CSingleton* s2 = CSingleton::getInstance();
        
        printf("s1 = %p
    ", s1);
        printf("s2 = %p
    ", s2);
          
        return 0;
    }

    (2)饿汉式:其特点是一开始就加载了,典型的“空间换时间”作法。因为一开始就创建了实例,所以每次用时直接返回就好了

    //创建型模式:单例模式
    
    #include <stdio.h>
    
    //饿汉式:多线程安全
    class CSingleton
    {
    public:
        //提供全局访问的访问点
        static CSingleton* getInstance()
        {
            static CSingleton instance;
            return &instance;
        }
       
    private:
        //将构造函数设为私有属性
        CSingleton(){};  
    };
    
    int main()
    {
        CSingleton* s1 = CSingleton::getInstance();
        CSingleton* s2 = CSingleton::getInstance();
        
        printf("s1 = %p
    ", s1);
        printf("s2 = %p
    ", s2);
          
        return 0;
    }

    (3)双重检查

    //创建型模式:单例模式
    
    #include <stdio.h>
    #include <windows.h>
    
    class Lock
    {
    private:
        HANDLE m_hMutex;
    public:
        Lock(){m_hMutex = CreateMutex(NULL, FALSE, NULL);}   
        ~Lock(){CloseHandle(m_hMutex);}
        
        void lock(){WaitForSingleObject(m_hMutex, INFINITE);}
        void unlock(){ReleaseMutex(m_hMutex);}
    };
    
    //多线程下的单例模式:处理懒汉式,因为饿汉式是线程安全的
    class CSingleton
    {
    public:
        //提供全局访问的访问点
        static CSingleton* getInstance()
        {
            //双检查
            if(m_pInstance == NULL) //第1次检查
            {
                m_lock.lock();
                if(m_pInstance == NULL) //第2次检查
                {
                    m_pInstance = new CSingleton;
                }
               m_lock.unlock();
            }
            return m_pInstance;
        }
       
    private:
        //将构造函数设为私有属性
        CSingleton(){}; 
        static CSingleton* m_pInstance; 
        static Lock m_lock;    
    };
    
    CSingleton* CSingleton::m_pInstance = NULL;
    Lock CSingleton::m_lock;
    
    //线程函数,用来测试的
    DWORD WINAPI ThreadProc(PVOID pvParam)
    {
        CSingleton* s = CSingleton::getInstance();
        
        printf("thread:%d, address = %p
    ",(int)pvParam, s);
        
        Sleep(1000);
    
        return 0;
    }
    
    int main()
    {
        const int iCount = 64; 
        HANDLE hThread[iCount];
    
        for(int i = 0; i< iCount; i++)
        {
            hThread[i] = CreateThread(NULL,0,ThreadProc,(LPVOID)i,0,NULL);
        }
        
        //注意:WaitForMultipleObjects最多能等待64个内核对象
        WaitForMultipleObjects(iCount, hThread, TRUE, INFINITE);
       
        for(int i = 0; i< iCount; i++)
            CloseHandle(hThread[i]);  
        
        return 0;
    }

    3. 单例模式的应用

    (1)单例模式的优点

      ①由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。

      ②由于单例模式只生成一个实例,所以减少了系统性能开销,当一个对象的产生需要较多资源时,可以启用一个单例对象然后长驻内存又节约内存空间。

      ③单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。

      ④单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

    (2)单例模式的缺点

      ①单例模式一般没有接口,扩展很困难。因其“自动实例化”,而抽象类和接口是不能被实例化的。所以不能增加接口

      ②单例模式对测试不利。在并行的开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也就不能虚拟一个对象。

    (3)使用的注意事项

      ①首先在高并发的情况下,要注意单例模式的线程同步问题。

      ②单例类一般不会主动要求被复制的,因此复制构造函数和赋值构造函数一般也设为私有。

    4. 单例模式的使用场景

    (1)多线程之间共享一个资源或者操作同一个对象

    (2)在整个程序空间中使用全局变量,共享资源

    (3)在创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源。

    (4)大规模系统中,为了性能的考虑,需要节省对象的创建时间

    (5)要求生成唯一序列号的环境或用单例做为一个计数器。

    5. 单例模式的扩展

    (1)可以在单例类,定义产生实例的数量和列表,来控制创建的实例数量,这叫多例模式

    (2)多例是单例的一种扩展,采用有上限的多例模式,可以在设计时决定内存中有多少个实例,以修正单例可能存在的性能问题,提高系统的响应速度。

  • 相关阅读:
    leetcode-383-Ransom Note(以空间换时间)
    AtCoder
    AtCoder
    Hadoop序列化案例实操
    Java实现MapReduce Wordcount案例
    HDFS常用API操作 和 HDFS的I/O流操作
    HBase常用的JAVA API操作
    ZooKeeper之服务器动态上下线案例
    机器学习(6)——逻辑回归
    机器学习(5)——多项式回归与模型泛化
  • 原文地址:https://www.cnblogs.com/5iedu/p/5480719.html
Copyright © 2020-2023  润新知