单例模式简介
单例模式是面向对象程序设计中比较常用的设计模式,一个类在程序的整个生命周期中只能产生不超过一个对象。(注意:这里为什么说不超过一个对象,而不是有且只有一个对象。在使用懒汉式单例模式下,如果这个单例模式的类一直没有使用的话,这个类就不会产生对象,即0个对象。如果使用了这个类,不管有多少处使用,这个类也只能产生1个对象。当然了,还有题外话:假如不使用这个类的话,为啥要设计这个类呢?)
单例模式的要点有三个
1.这个类只能有一个实例;
2.这个类必须自行创建这个实例;
3.这个类必须自行向整个系统提供这个实例。
对于单利模式的要点,在单例模式的实现角度,包括下列三点
1.为了防止使用者通过new操作(类的默认构造函数),赋值操作(类的默认重载赋值运算符)生成多个实例,这个类的默认构造函数,默认的重载赋值运算符必须设置为私有的;
2.类中定义一个私有的该类的对象,类必须自行创建这个唯一的对象并自己保存;
3.类中需要提供一个公共的静态函数,能够给使用者获取到类自己保存的这个唯一对象,从而能够获取到这个类提供的其他真正提供具体服务的方法。
常见的两种实现方式
1.饿汉方式
在类被加载的时候,就直接初始化一个该类的对象。后面不管这个类有没有被使用,对象都一直存在。
2.懒汉方式
在类真正在使用的时候,才会初始化一个该类的对象。在没有使用这个类之前,这个对象都不在。
c++实现
1.饿汉方式
静态对象版本示例代码
头文件
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 }
运行结果
个人理解:静态指针版本不需要把声明私有的拷贝构造函数和重载赋值运算符,因为对与这两种操作,在指针的操作后最终拿到的还是同一个对象的地址。
2.懒汉方式
单例类的对象在真正使用的时候才去创建,简单的实现如下
头文件
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。
3.改进的懒汉方式
头文件
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 }
测试文件和测试结果跟上面一样。
此时,由于判断对象是否存在前已经加锁了,因此多线程情况下也能正常执行了。
但是引入了另外一个问题,每次在客户端的线程(或者进程)调用的时候都要加锁,导致效率不高。为了解决这个问题,下面对这个显示进行改进。
4.进一步改进的懒汉方式
头文件不变,测试文件不变,实现文件如下
#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语句中执行加锁操作,直接就把现有的实例返回了,因此后续的请求也能比较完美的解决了。
综上所述,使用双重判定的懒汉方式仅会在没有生成对象的第一波请求的时候才会效率较低,后续所有的请求由于都不存在加锁等操作,效率也能提上来了。