一.概述:

因为在设计或开发中,肯定会有这么一种情况,一个类只能有一个对象被创建,如果有多个对象的话,可能会导致状态的混乱和不一致。这种情况下,单例模式是最恰当的解决办法。有很多地方需要这样的功能模块,如系统的日志输出,GUI应用必须是单鼠标,MODEM的联接需要一条且只需要一条电话线,操作系统只能有一个窗口管理器,一台PC连一个键盘。单例模式有很多种实现方式,各自的特性不相同,使用的情形也不相同。今天要实现的是常用的三种,分别是饿汉式、懒汉式和多线程式。

《设计模式》一书中的实现有三个要素,定义一个单例类,要使用类的私有静态指针变量指向类的唯一实例(即在类中就生成一个对象),并用一个公有的静态方法获取该实例,并把构造函数定义为protected或private。




二.懒汉式实现单例模式:

懒汉式的特点是延迟加载,懒汉么,很懒,它只在要用到实例时才加载实例。

/****************************************2>FileName:lanhan.cpp3>Author:xiaoxiaohui4>mail:1924224891@qq.com5>CreatedTime:2016年05月07日星期六15时01分25秒6****************************************/78#include<iostream>9usingnamespacestd1011classSingleton12{13private:14Singleton()15{}16staticSingleton*_instace;//静态的私有的17public:18staticSingleton*GetInstace()19{20if(_instance==NULL)21{22_instance=newSingleton();23}24return_instance;//如果非空则new一个对象后者返回原来的两个对象(所以保证了只有一个对象生成)25}2627}28

上面的这一实现存在内存泄露问题,因为没有释放_instance指针,下面为懒汉式的改进版:

8#include<iostream>9usingnamespacestd1011classSingleton12{13private:14Singleton()15{}16staticSingleton*_instance;//静态的私有的1718classdel19{20public:21~del()22{23if(Singleton::_instance!=NULL)24{25deleteSingleton::_instance;26Singleton::_instance=NULL;27}28}29}30staticdeld;//静态变量会在程序结束时调用它的析构函数31public:32staticSingleton*GetInstance()33{34if(_instance==NULL)35{36_instance=newSingleton();37}38return_instance;//如果非空则new一个对象后者返回原来的两个对象(所以保证了只有一个对象生成)39}4041}

该实现会在程序结束时调用静态变量的析构函数,从而delete了唯一的Singleton对象。

使用这种方法释放单例对象有以下特征:
1.在单例类内部定义专有的嵌套类。
2.在单例类内定义私有的专门用于释放的静态成员。
3.利用程序在结束时析构全局变量的特性,选择最终的释放时机。


但是现在还有问题,如果在多线程环境下,因为“if(_instance == NULL)”并不是原子的,会存在线程安全问题(如果一个线程刚刚判断了指针为空,这时另一个线程的优先级更高或者其它原因,打断了原来线程的执行,再次判断指针也会为空,所以会出现两个实例)下面为多线程环境下的懒汉式单例模式:

8#include<iostream>9#include<stdlib.h>10#include<pthread.h>1112pthread_mutex_tlock=PTHREAD_MUTEX_INITIALIZER;13usingnamespacestd1415classSingleton16{17private:18Singleton()19{}20staticSingleton*_instance;//静态的私有的2122classdel23{24public:25~del()26{27if(Singleton::_instance!=NULL)28{29deleteSingleton::_instance;30Singleton::_instance=NULL;31}32}33}34staticdeld;//静态变量会在程序结束时调用它的析构函数35public:36staticSingleton*GetInstance()37{38pthread_mutex_lock(&lock);39if(_instance==NULL)40{41_instance=newSingleton();42}43pthread_mutex_unlock(&lock);44return_instance;//如果非空则new一个对象后者返回原来的两个对象(所以保证了只有一个对象生成)45}4647}48

但现在还有问题,当有大量的线程时,只会有一个线程进入互斥锁,然后执行下面的代码而其它线程只能等待,并且加锁是一个繁重的过程,这样会导致加很多次锁,这样就太不高效了。下面是高效版的多线程环境下的懒汉式单例模式:

8#include<iostream>9#include<stdlib.h>10#include<pthread.h>1112pthread_mutex_tlock=PTHREAD_MUTEX_INITIALIZER;13usingnamespacestd1415classSingleton16{17private:18Singleton()19{}20staticSingleton*_instance;//静态的私有的2122classdel23{24public:25~del()26{27if(Singleton::_instance!=NULL)28{29deleteSingleton::_instance;30Singleton::_instance=NULL;31}32}33}34staticdeld;//静态变量会在程序结束时调用它的析构函数35public:36staticSingleton*GetInstance()37{38if(_instance==NULL)39{40pthread_mutex_lock(&lock);41if(_instance==NULL)42{43_instance=newSingleton();44}45pthread_mutex_unlock(&lock);46}47return_instance;//如果非空则new一个对象后者返回原来的两个对象(所以保证了只有一个对象生成)48}4950}

这样只有没有Singleton实例时才会进入加锁的代码,而当有Singleton实例时不需要进入加锁的代码中,直接返回已存在的实例就行了。




三.饿汉式的单例模式:在一开始就创建实例,要用时直接返回即可。饿汗式的单例模式没有线程安全问题,因为所以线程都只能访问一个已存在的对象,无论线程怎么调度都不会有多个对象出现。因为对象是一个静态变量(不是指针),会在程序结束时自动调用它的析构函数,所以不用考虑内存泄露问题。

饿汗式的特点:代码简单,不会出现内存泄露,是线程安全的。

1/****************************************2>FileName:erhan.cpp3>Author:xiaoxiaohui4>mail:1924224891@qq.com5>CreatedTime:2016年05月07日星期六16时10分56秒6****************************************/78#include<iostream>9usingnamespacestd101112classSingleton13{14private:15Singleton()16{}17staticSingletoninstance;//静态变量只会有一份数据存在从而保证只有一个实例18public:19staticSingleton&GetInstance()20{21returninstance;22}23}

声明一个局部的静态变量,而静态变量在全局范围内只有一份数据,所以无论调用多少此GetInstance,返回的都是那一个实例。

但这个实现存在问题,Singleton singleton = Singleton :: GetInstance(),这么做就出现了一个类拷贝的问题,这就违背了单例的特性。产生这个问题原因在于:因为在这里没有实现拷贝构造函数,编译器会为类生成一个默认的拷贝构造函数,来支持类的拷贝。

解决方法:1.自己再定义一个拷贝构造函数和operator=,这个拷贝构造函数和operator=什么都不做。

2.返回一个Singleton指针。

下面为方法2的代码:

8#include<iostream>9usingnamespacestd101112classSingleton13{14private:15Singleton()16{}17staticSingletoninstance;//静态变量只会有一份数据存在从而保证只有一个实例18public:19staticSingleton*GetInstance()20{21return&instance;22}23}




总结:单例模式适用于只允许一个实例存在的情况,它的实现必须满足三个条件,一是必须在类中就定义一个实例;二是必须有一个公有的静态方法来获取该实例;三是构造函数必须是私有的,来保证不容许别人通过调用构造函数来生成一个实例。在实现时要注意内存泄露问题,线程安全问题,性能问题。