定义 : 确保某一个类只有一个实例 , 而且自行实例化并向整个系统提供这个实例 . 单例模式应该是23种设计模式中最简单的一种模式了 . 它有以下几个要素:
私有的构造方法指向自己实例的私有静态引用以自己实例为返回值的静态的公有的方法UML图 :
单例模式根据实例化对象时机的不同分为两种:一种是饿汉式单例模式,一种是懒汉式单例模式 .
定义 : 即类产生的时候就创建好实例对象,这是一种空间换时间的方式; 代码如下:
//饿汉单例模式 #if 0 class Singleton { public: //获取唯一可用的对象 static Singleton* getInstance() { return &singleton; } //类中其他方法 , 尽量是static static void doSomething() { cout<<"我就是皇帝某某某....."<<endl; } private : //单例对象在这里 static Singleton singleton ; //让构造函数为 private,这样该类就不会被实例化 , 限制产生多个对象 Singleton() {cout<<"单例对象创建"<<endl;} ~Singleton() {cout<<"单例对象销毁"<<endl;} }; //静态数据成员初始化 , 分配内存 //<数据类型><类名>::<静态数据成员名>=<值> Singleton Singleton::singleton ; int main() { Singleton *p1 = Singleton::getInstance(); Singleton *p2 = Singleton::getInstance(); Singleton *p3 = Singleton::getInstance(); //只有一个地址 cout<<"p1: "<<p1<<endl<<"p2: "<<p2<<endl<<"p3: "<<p3<<endl; return 0; } #endif运行结果:
饿汉单例模式是线程安全的,不需要考虑线程同步!
定义 : 即在需要的时候,才创建对象,这是一种时间换空间的方式 ; 注 : 这是一种线程不安全的单例 . 我们后面来说. 代码如下 :
//懒汉单例模式 #if 0 //线程不安全的单例 class Singleton { public: //注意这里和饿汉模式不同 static Singleton* getInstance() { if(singleton == nullptr) { singleton = new Singleton(); } return singleton; } static void doSomething() { cout<<"我就是皇帝某某某....."<<endl; } private : //注意这里 , 和饿汉模式声明的成员不同 static Singleton* singleton ; Singleton() {cout<<"单例对象创建"<<endl;} ~Singleton() {cout<<"单例对象销毁"<<endl;} //定义一个内部类 //如果单例模式的类中申请了其他资源,就无法释放,导致内存泄漏! //原因:此时全局数据区中,存储的并不是一个实例对象,而是一个实例对象的指针,即一个地址变量而已! //实例对象呢?在堆区,因为是通过new得来的!虽然这样能够减小全局数据区的占用,把实例对象这一大坨都放到堆区。 //可是!如何释放资源呢? //定义内部类 class CGarbo { public: CGarbo() {} ~CGarbo() { if(singleton != nullptr) { delete singleton; singleton = nullptr; } } }; // 定义一个内部类的静态对象 // 当该对象销毁时,顺带就释放singleton指向的堆区资源 static CGarbo m_garbo; }; Singleton* Singleton::singleton = nullptr; Singleton::CGarbo Singleton::m_garbo; int main() { Singleton *p1 = Singleton::getInstance(); Singleton *p2 = Singleton::getInstance(); Singleton *p3 = Singleton::getInstance(); cout<<"p1: "<<p1<<endl<<"p2: "<<p2<<endl<<"p3: "<<p3<<endl; return 0; } #endif运行结果:
在这里解释一下为什么使用内部类 , 这是因为如果在类A(如 : Singleton)的析构函数中delete 类A的对象 , delete 还会调用类A的析构函数 , 这样就造成了一个死循环 . 所以要采用内部类的方式进行析构 , 防止内存的泄漏 .
饿汉单例模式也可以写成这种形式 , 将static Singleton singleton ; 改为 static Singleton* singleton ; , 这里也需要使用内部类 .初始化的方式和懒汉单例模式一样.
我们再来说说它为什么是线程不安全的单例. 该单例在低并发的情况下尚不会出现问题 , 若系统压力增大 , 并发量增加时则可能在内存中出现多个实例 , 破坏了最初的预期 . 为什么会出现这种情况呢?如一个线程A执行到 singleton = new Singleton(); , 但还没有获得对象(对象的初始化是需要时间的) , 第二个线程B也在执行 , 执行到if(singleton == nullptr) 判断 , 那么线程B获得的判断条件也是为真 , 于是继续运行下去 , 线程A获得了一个对象 , 线程B也获得了一个对象 , 在内存中就出现两个对象 !
可以通过加锁的方式来解决.
static Singleton* getInstance() { lock(); //伪代码 , 具体实现请查阅资料 if(singleton == nullptr) { singleton = new Singleton(); } unlock(); //伪代码 return singleton; }如果一个类可以产生多个对象,对象的数量不受限制,则是非常容易实现的,直接使用new关键字就可以了,如果只需要一个对象,使用单例模式就可以了,但是如果要求一个类只 能产生两三个对象呢?该怎么实现?
这种需要产生固定数量对象的模式就叫做有上限的多例模式,它是单例模式的一种扩展,采用有上限的多例模式,我们可以在设计时决定在内存中有多少个实例 , 便系统进行扩展 , 修正单例可能存在的性能问题,提供系统的响应速度 .例如读取文件 , 我们可以在系统启动时完成初始化工作 , 在内存中启动固定数量的reader实例 , 然后在需要读取文件时就可以快速相应 .