第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\n", s1);
    printf("s2 = %p\n", 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\n");
            }
        }
    };
    
    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\n", s1);
    printf("s2 = %p\n", 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\n", s1);
    printf("s2 = %p\n", 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\n",(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)多例是单例的一种扩展,采用有上限的多例模式,可以在设计时决定内存中有多少个实例,以修正单例可能存在的性能问题,提高系统的响应速度。

猜你喜欢

转载自blog.csdn.net/CherishPrecious/article/details/83855250
今日推荐