C++单例模式(懒汉和饿汉)与线程安全

来源:互联网 发布:软件可靠性和健壮性 编辑:程序博客网 时间:2024/06/05 13:56

单例:

单例大约有两种实现方法:懒汉与饿汉。
懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化,
饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。

(1)饿汉

饿汉单例,即在最开始的时候,静态对象就已经创建完成;
设计方法是类中包含一个静态成员指针,该指针指向该类的一个对象,提供一个公有的静态成员方法,返回该对象指针;为了使得对象唯一,还需要将构造函数设为私有,代码如下:

class Sigletion{//构造函数为私有;Sigletion(){cout << "Sigletion()" <<endl;}static Sigletion * intance; //静态成员,指向Sigletion对象的指针。public://提供静态共有方法,可以使用类名加域名进行访问,返回对象指针;static Sigletion* GetSigletion(){return intance;}};Sigletion* Sigletion:: intance = new Sigletion;


测试代码:

int main(){/*我们注意到,在刚进入main函数,我们就打印了一句“”main begin“”,为什么要在这里加入一句输出语句呢?因为我们此时的单例模式采用的是饿汉单例,所以,哪怕你还没用到我这个对象,我也会先创建一个出来,先占着,即所谓的饿汉,其实就是类似于全局变量的构造是在进入main函数之前的原理,*/cout << "main begin" <<endl; //验证静态对象的创建在main之前。//验证无法创建实例。//Sigletion tmp;//Sigletion *ptr = new Sigletion;//调用共有的静态成员方法Sigletion *ptr = Sigletion::GetSigletion();Sigletion *ptr2 = Sigletion::GetSigletion();if (ptr == ptr2){cout << "yes" <<endl;}else{cout << "no"<<endl;}cout << "hello..." <<endl;system("pause");return 0;}


单例的饿汉实现是线程安全的,因为对象在使用前就已经创建出来了。

(2)懒汉

所谓懒汉模式,就是尽可能晚的创建这个对象的实例,即在单例类第一次被引用时将自己初始化;其实C++里很多地方都是类似这样的思想,比如晚绑定,写时拷贝技术等,就是尽量使资源的利用最大化,不要让空闲的人还占着有限的资源。

class Sigletion2{Sigletion2(){cout << "Sigletion2()" <<endl;}static Sigletion2* intance2;public:static Sigletion2* GetSigletion2(){if (intance2 == NULL){intance2 = new Sigletion2();cout << "it is once" <<endl;}else{cout << "it is not once" <<endl;}return intance2;}};Sigletion2* Sigletion2:: intance2 = NULL;  //先初始化为空,等真正用上这个单例的时候再创建这个例。


(3)懒汉的线程安全问题

如果此时多线程进行操作,简单点以两个线程为例,假设pthread_1刚判断完 intance 为NULL 为真,准备创建实例的时候,切换到了pthread_2, 此时pthread_2也判断intance为NULL为真,创建了一个实例,再切回pthread_1的时候继续创建一个实例返回,那么此时就不再满足单例模式的要求了, 既然这样,是因为多线程访问出的问题,那我们就来加把锁,使得线程同步;

class singleton{private:singleton(){pthread_mutex_init(&mutex);}static singleton* p;static pthread_mutex_t mutex;public:static singleton* initance(){pthread_mutex_lock(&mutex);if (p == NULL){p = new singleton();}pthread_mutex_unlock(&mutex);return p;}};pthread_mutex_t singleton::mutex;singleton* singleton::p = NULL;

那么我们这样写的代码是没有问题的,但是效率可能有点低,因为加锁是一个非常耗时的操作,没有必要每次都要加锁解锁,只有刚开始创建对象的时候需要加锁。如果对象都

创建出来了,就没必要加锁解锁了,直接返回这个对象的指针。

class singleton{private:singleton(){pthread_mutex_init(&mutex);}static singleton* p;static pthread_mutex_t mutex;public:static singleton* initance(){if (p == NULL)   //p != NULL,说明对象已经创建出来了,直接返回对象的指针,没必要在加锁解锁浪费时间。{pthread_mutex_lock(&mutex);if (p == NULL){p = new singleton();}pthread_mutex_unlock(&mutex);}return p;}};pthread_mutex_t singleton::mutex;singleton* singleton::p = NULL;


允许可变数目的实例,基于单例模式我们可以扩展,使用与单例控制相似的方法来获得指定个数的对象实例。(即单例类内有多个静态对象指针成员,每次当单例类被引用时随机分配一个实例对象);


单例模式的适用场景
(1)系统只需要一个实例对象,或者考虑到资源消耗的太大而只允许创建一个对象。
(2)客户调用类的单个实例只允许使用一个公共访问点,除了该访问点之外不允许通过其它方式访问该实例 (就是共有的静态方法)。

0 0
原创粉丝点击