单例模式是面向对象程序设计中比较经常使用的设计模式,一个类在程序的整个生命周期中只能产生不超过一个对象。(注意:这里为何说不超过一个对象,而不是有且只有一个对象。在使用懒汉式单例模式下,若是这个单例模式的类一直没有使用的话,这个类就不会产生对象,即0个对象。若是使用了这个类,无论有多少处使用,这个类也只能产生1个对象。固然了,还有题外话:假如不使用这个类的话,为啥要设计这个类呢?)ios
1.这个类只能有一个实例;c++
2.这个类必须自行建立这个实例;设计模式
3.这个类必须自行向整个系统提供这个实例。多线程
对于单利模式的要点,在单例模式的实现角度,包括下列三点函数
1.为了防止使用者经过new操做(类的默认构造函数),赋值操做(类的默认重载赋值运算符)生成多个实例,这个类的默认构造函数,默认的重载赋值运算符必须设置为私有的;测试
2.类中定义一个私有的该类的对象,类必须自行建立这个惟一的对象并本身保存;spa
3.类中须要提供一个公共的静态函数,可以给使用者获取到类本身保存的这个惟一对象,从而可以获取到这个类提供的其余真正提供具体服务的方法。线程
1.饿汉方式设计
在类被加载的时候,就直接初始化一个该类的对象。后面无论这个类有没有被使用,对象都一直存在。指针
2.懒汉方式
在类真正在使用的时候,才会初始化一个该类的对象。在没有使用这个类以前,这个对象都不在。
头文件
1 #ifndef __SINGLETON_H__ 2 #define __SINGLETON_H__ 3 4 class CMySingleton 5 { 6 public: 7 //提供一个获取该实例的方法 8 static CMySingleton& getInstance(); 9 //真正提供服务的具体方法 10 void invoke1(); 11 12 private: 13 //防止经过默认构造函数建立新对象 14 CMySingleton(); 15 //默认析构函数 16 ~CMySingleton(); 17 //防止经过拷贝构造函数和赋值运算符建立新对象 18 CMySingleton(const CMySingleton&); 19 CMySingleton& operator=(const CMySingleton&); 20 private: 21 static CMySingleton m_pInstance; 22 }; 23 24 #endif // !__SINGLETON_H__
实现文件
1 #include "singleton.h" 2 #include <iostream> 3 4 CMySingleton CMySingleton::m_pInstance;//static 成员须要类外定义 5 6 CMySingleton::CMySingleton() 7 { 8 } 9 10 CMySingleton::~CMySingleton() 11 { 12 } 13 14 CMySingleton& CMySingleton::getInstance() 15 { 16 return m_pInstance; 17 } 18 19 void CMySingleton::invoke1() 20 { 21 std::cout << "call invoke1" << std::endl; 22 }
测试文件
1 #include "singleton.h" 2 #include <iostream> 3 4 using namespace std; 5 6 int main() 7 { 8 //调用单例类的具体方法 9 CMySingleton::getInstance().invoke1(); 10 11 return 0; 12 }
头文件
1 #ifndef __SINGLETON_H__ 2 #define __SINGLETON_H__ 3 4 class CMySingleton 5 { 6 public: 7 //提供一个获取该实例的方法 8 static CMySingleton* getInstance(); 9 //真正提供服务的具体方法 10 void invoke1(); 11 12 private: 13 //防止经过默认构造函数建立新对象 14 CMySingleton(); 15 //默认析构函数 16 ~CMySingleton(); 17 private: 18 static CMySingleton* m_pInstance; 19 }; 20 21 #endif // !__SINGLETON_H__
实现文件
1 #include "singleton.h" 2 #include <iostream> 3 4 CMySingleton* CMySingleton::m_pInstance = new CMySingleton();//static 成员须要类外定义,直接建立静态指针对象 5 6 CMySingleton::CMySingleton() 7 { 8 } 9 10 CMySingleton::~CMySingleton() 11 { 12 if (m_pInstance) 13 { 14 delete m_pInstance; 15 m_pInstance = nullptr; 16 } 17 } 18 19 CMySingleton* CMySingleton::getInstance() 20 { 21 return m_pInstance; 22 } 23 24 void CMySingleton::invoke1() 25 { 26 std::cout << "call invoke1" << std::endl; 27 }
测试文件
1 #include "singleton.h" 2 #include <iostream> 3 4 using namespace std; 5 6 int main() 7 { 8 //调用单例类的具体方法 9 CMySingleton::getInstance()->invoke1(); 10 11 return 0; 12 }
运行结果
我的理解:静态指针版本不须要把声明私有的拷贝构造函数和重载赋值运算符,由于对与这两种操做,在指针的操做后最终拿到的仍是同一个对象的地址。
单例类的对象在真正使用的时候才去建立,简单的实现以下
头文件
1 #ifndef __SINGLETON_H__ 2 #define __SINGLETON_H__ 3 4 #include <iostream> 5 6 class CMySingleton 7 { 8 public: 9 //提供一个获取该实例的方法 10 static CMySingleton* getInstance(); 11 //真正提供服务的具体方法 12 void invoke1() 13 { 14 std::cout << "call invoke1" << std::endl; 15 } 16 17 private: 18 //防止经过默认构造函数建立新对象 19 CMySingleton() 20 //默认析构函数 21 ~CMySingleton() 22 private: 23 static CMySingleton* m_pInstance; 24 }; 25 26 #endif // !__SINGLETON_H__
实现文件
1 #include "singleton.h" 2 #include <iostream> 3 4 CMySingleton* CMySingleton::m_pInstance = nullptr;//static 成员须要类外定义,定义的时候不建立对象,在真正使用的时候才建立 5 6 CMySingleton::CMySingleton() 7 { 8 } 9 10 CMySingleton::~CMySingleton() 11 { 12 if (m_pInstance) 13 { 14 delete m_pInstance; 15 m_pInstance = nullptr; 16 } 17 } 18 19 CMySingleton* CMySingleton::getInstance() 20 { 21 if (m_pInstance == nullptr) 22 { 23 m_pInstance = new CMySingleton(); 24 } 25 return m_pInstance; 26 } 27 28 void CMySingleton::invoke1() 29 { 30 std::cout << "call invoke1" << std::endl; 31 }
测试文件
1 #include "singleton.h" 2 #include <iostream> 3 4 using namespace std; 5 6 int main() 7 { 8 //调用单例类的具体方法 9 CMySingleton::getInstance()->invoke1(); 10 11 return 0; 12 }
运行结果与饿汉式同样。
在单线程状况下运行程序执行没有问题,可是在多线程环境下,在未建立实例的时候若是同时有多个进程请求这个类的服务,因为m_pInstance == nullptr为True,将会致使同时建立多个实例。所以在建立实例的时候加锁判断实例是否已经建立,若是未建立才须要new。
头文件
1 #ifndef __SINGLETON_H__ 2 #define __SINGLETON_H__ 3 4 #include <iostream> 5 #include <pthread.h> 6 7 class CMySingleton 8 { 9 public: 10 //提供一个获取该实例的方法 11 static CMySingleton* getInstance(); 12 //真正提供服务的具体方法 13 void invoke1() 14 { 15 std::cout << "call invoke1" << std::endl; 16 } 17 18 private: 19 //防止经过默认构造函数建立新对象 20 CMySingleton(); 21 //默认析构函数 22 ~CMySingleton(); 23 private: 24 static CMySingleton* m_pInstance; 25 static pthread_mutex_t m_mutex; 26 }; 27 28 class AutoLock 29 { 30 public: 31 AutoLock(pthread_mutex_t* mutex): m_mutex(mutex) 32 { 33 pthread_mutex_lock(m_mutex); 34 } 35 ~AutoLock() 36 { 37 pthread_mutex_unlock(m_mutex); 38 } 39 40 private: 41 pthread_mutex_t* m_mutex; 42 43 }; 44 45 #endif // !__SINGLETON_H__
此处使用了pthread的互斥量来做锁操做,同时定义了一个AutoLock的类用来加锁(此类不是必须,只要在实现里面可以在判断前正确加锁便可)。
实现文件
1 #include "singleton.h" 2 #include <pthread.h> 3 4 CMySingleton* CMySingleton::m_pInstance = nullptr;//static 成员须要类外定义,定义的时候不建立对象,在真正使用的时候才建立 5 pthread_mutex_t CMySingleton::m_mutex = PTHREAD_MUTEX_INITIALIZER; 6 7 CMySingleton::CMySingleton() 8 { 9 } 10 11 CMySingleton::~CMySingleton() 12 { 13 pthread_mutex_destroy(&m_mutex); 14 if (m_pInstance) 15 { 16 delete m_pInstance; 17 m_pInstance = nullptr; 18 } 19 } 20 21 CMySingleton* CMySingleton::getInstance() 22 { 23 AutoLock lock(&m_mutex); 24 if (m_pInstance == nullptr) 25 { 26 m_pInstance = new CMySingleton(); 27 } 28 return m_pInstance; 29 }
测试文件和测试结果跟上面同样。
此时,因为判断对象是否存在前已经加锁了,所以多线程状况下也能正常执行了。
可是引入了另一个问题,每次在客户端的线程(或者进程)调用的时候都要加锁,致使效率不高。为了解决这个问题,下面对这个实现进行改进。
头文件不变,测试文件不变,实现文件以下
#include "singleton.h" CMySingleton* CMySingleton::m_pInstance = nullptr;//static 成员须要类外定义,定义的时候不建立对象,在真正使用的时候才建立 pthread_mutex_t CMySingleton::m_mutex = PTHREAD_MUTEX_INITIALIZER; CMySingleton::CMySingleton() { } CMySingleton::~CMySingleton() { pthread_mutex_destroy(&m_mutex); if (m_pInstance) { delete m_pInstance; m_pInstance = nullptr; } } CMySingleton* CMySingleton::getInstance() { if (m_pInstance == nullptr) { AutoLock lock(&m_mutex); if (m_pInstance == nullptr) { m_pInstance = new CMySingleton(); } } return m_pInstance; }
此处在加锁前多一次判断实例是否存在(双重断定),所以,在第一次即便多个线程(或者进程)进来的时候第一次判断都是空,此时多个线程调用都会竞争锁资源,获取到说资源的调用会建立第一个实例并返回后,以前竞争锁资源的线程或者进程再判断,实例已经建立,所以不会再从新new。第一波的处理能够比较完美的解决了。
后续继续有客户端的请求进来,因为再第一层判断实例已经存在,即m_pInstance != nullptr,所以也不会进入if语句中执行加锁操做,直接就把现有的实例返回了,从然后续的请求也能比较完美的解决了。
综上所述,使用双重断定的懒汉方式仅会在没有生成对象的第一波请求的时候才会效率较低,后续全部的请求因为都不存在加锁等操做,效率也能提上来了。