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

单例:

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

(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)客户调用类的单个实例只允许使用一个公共访问点,除了该访问点之外不允许通过其它方式访问该实例 (就是共有的静态方法)。

  • 6
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值