单例模式(Singleton)
--本文内容部分引自《大话设计模式 Chapter21》
一.概念:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
通常我们可以让一个全局变量使一个对象被访问,但它不能阻止你实例化多个对象,一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。
二.结构:
单例模式因为Singleton类封装它的唯一实例,这样它可以严格的控制客户怎样访问它及何时访问它,简单来说就是对唯一实例的受控访问。
单例类有状态,虽然实例唯一,却可以有子类来继承。
特点:
1、构造函数私有,防止外部实例化。这是第一点要求
2、唯一实例句柄声明为static,这就是所谓唯一
三.单线程模式:
1 class CSingleton 2 { 3 public: 4 static CSingleton *GetInstance() 5 { 6 if (NULL != m_Instance) 7 { 8 m_Instace = new CSingleton(); 9 } 10 } 11 12 private: 13 CSingleton(); 14 15 private: 16 static CSingleton * m_Instance; 17 }; 18 //此处初始化 19 CSingleton* CSingleton::m_Instace = NULL;
这里相应有一些可以提升的地方,比如实际实例类型不定的情况下,这里可以改为使用模板,类型待编译时刻再定;仅仅私有构造,还有其他方式会导致实例化,如拷贝构造。
1 template2 class CSingleton 3 { 4 public: 5 static T *GetInstance() 6 { 7 if (NULL != m_Instance) 8 { 9 m_Instace = new T; 10 } 11 } 12 13 private: 14 CSingleton(); 15 CSingleton(const T&); 16 void operator=(const T&); 17 18 private: 19 static T * m_Instance; 20 }; 21 22 //此处初始化 23 template 24 T* CSingleton ::m_m_Instance= NULL;
四、多线程模式:
首先,提到多线程,那就马上要考虑同步的问题了,说到底就是锁,具体锁怎么实现这里不赘叙。
5 static T *GetInstance() 6 { 7 m_mutex.Lock(); 8 if (NULL != m_Instance) 9 { 10 m_Instance = new T; 11 } 12 m_mutex.UnLock(); 13 }
还有需要考虑一个会遇到的多线程情况下遇到的问题,那就是上面写法的锁的位置,当句柄为NULL时,同时有两个线程调用到了GetInstance入口,拿到时间片的线程进入if里面new出对象,结束后另一个线程进入后,依旧会再new一次。这种可能是存在的。
为了应对这种低概率但又不能无视的情况,大神们给出了方案--双重锁定(Double-Check Locking).
GetInstance函数修改如下:
1 static T *GetInstance() 2 { 3 if (NULL != m_Instance) 4 { 5 m_mutex.Lock(); 6 if (NULL != m_Instance) 7 { 8 m_Instance = new T; 9 } 10 m_mutex.UnLock(); 11 } 12 }
五、总结
1.以上所有代码实现都是所谓的懒汉式单例类,因为是在第一次被引用时才会将自己实例化。
相对有饿汉模式,就是在以上代码初始化的地方直接写成
1 template2 T* CSingleton::m_Instance = new T;
这种静态初始化的方式称为饿汉式单例类。
2.目前实际使用中,只在项目工程中的日志记录、配置文件操作、内存池等全局唯一实例上,使用起来还是很方便的。