【C++】设计模式------单例模式(懒汉和饿汉)

本文参考自《大话设计模式》,想借此记录一下对书本内容的理解,并以自己项目为例子采用C++语言进行举例。

概念

单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。通俗点讲,在程序当中创建了一个类,我们希望它仅能被实例化一次,产生一个对象。我们不能靠程序员去控制自己只能实例化一次,这是非常不保险的,我们需要代码内在机制帮助我们去控制这样的行为,这就是设计模式的意义。假设一个项目当中只能存在一个AGV(无人小车)对象。

三个要点

  • 某个类只能有一个实例
  • 它必须自行创建这个实例
  • 它必须自行向整个系统提供对这个实例的访问

方法

1.让new失效

所有类都有构造函数,如果没有编写构造函数,则编译器会使用默认构造函数。首先考虑到的是将构造函数写成private,这样堵死了外界利用new创建此类实例的可能。因为在外界调用new产生实例实质上也是调用构造函数。

public class AGV{
	private AGV(){}
}

2.建立静态变量

通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的方法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。

我们在类当中去保留这个唯一的实例化对象指针,并提供public函数去实例化/访问它,即设立一个static成员。在类中的static成员,代表所有的对象共用这一个成员,静态函数同理。

构造函数是私有的,但是GetInstance函数是内部成员函数,可以调用私有变量。函数内部首先要判断agvObject是否为空,如果为空,证明之前并没有创建,则通过new实例化一个对象。如果不为空,则证明之前已经创建了,则直接返回agvObject。此函数是获得本类实例的唯一全局访问点。

public class agvClass{
	private static agvClass* agvObject;
	private agvClass(){}

	public static agvClass GetInstance() {
		if (agvObject == null) {
			agvObject = new agvClass();
		}
		return agvObject;
	}
}

多线程时的单例

1.问题提出

在多线程时,如果多个线程同时访问GetInstance函数,则有可能造成创建多个实例。这和单例模式的初衷的不符的。为什么会造成这种情况呢?

想象这样一个场景,假设A线程已经运行到箭头所指的部分,此时判断语句已经结束,实例化new语句还没开始。B线程横刀夺爱,抢走了CPU使用权,同样也调用GetInstance函数。此时agvObject依然为null,因为A线程并没有运行new语句。那B线程就实例化了一个agvClass。

回到A线程,因为判断语句已经运行过了,所以不需要再次判断agvOject是否为空,也就错过了挽救的机会。直接运行new语句,再次实例化agvClass。这样系统就存在了两个agvClass。
【C++】设计模式------单例模式(懒汉和饿汉)_第1张图片

2.线程单锁

确保当一个线程位于代码的临界区时,另外一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待,直到该对象被释放。

publiac static agvClass GetInstance() {
		lock();  //加锁
		if (agvObject == null) {
			agvObject = new agvClass();
		}
		unlock(); //解锁
		return agvObject;
	}

3.双重锁定

如果是单锁,每次调用GetInstance函数都需要lock,影响性能。因此出现了Double-Check Locking(双重锁定)。

publiac static agvClass GetInstance() {
		if (agvObject == null) {
			lock();  //加锁
			if (agvObject == null) {
				agvObject = new agvClass();
			}
			unlock(); //解锁
		}
		return agvObject;
	}

如果实例不为空,直接返回agvObject,不需要加锁解锁等步骤。只有在实例未被创建的时候再加锁处理。那为什么需要判断两次agvObject为空?

第一次判断是在普通情况下,防止多次加锁解锁影响性能而设定的。

第二判断是用于两次线程同时进入GetInstance情况设定的。假设A线程停留在如箭头所指的部分,此时new语句还未运行。和上面情况类似,B线程横刀夺爱,也调用了GetInstance,它首先会通过第一个if判断,因此此时实例尚未创建,但会停留在lock这里,排队等候。当A线程完成实例的创建,B继续运行,如果此时没有第二次判断,就会创建第二个实例,不符合单例模式。
【C++】设计模式------单例模式(懒汉和饿汉)_第2张图片

懒汉模式和饿汉模式

1.懒汉模式

上面提到的模式就是懒汉模式,就是第一次用到类的示例才实例化。注意到,前面new语句放在GetInstance函数,这个函数需要外界调用才会发挥作用,然后new一个实例。形象点记忆,懒汉就是不到万不得已就不会去实例化类,我用不上那就先不管。懒汉模式是不安全的实现方式,是线程不安全的,需要加锁。

2.饿汉模式

类定义的时候就实例化,本身是线程安全,不需要加锁。饿汉,就是不管三七二十一,这个类编译的时候就实例化,不考虑后面用不用得上。用法如下所示,直接在类外实例化,即初始化即实例化。

public class agvClass{
	private static agvClass* agvObject;
	private agvClass(){}

	publiac static agvClass GetInstance() {
		return agvObject;
	}
}

agvClass* agvClass::agvObject = new agvClass;

3.选择

懒汉:在访问量比较小,采用懒汉模式。到有需要的时间才实例化对象,那它就不会提前占据内存空间,代价就是后续每次访问都会判断是否为空,增加时间成本。这是以时间换空间。

饿汉:在访问量比较大,或者可能访问的线程比较多,采用饿汉模式。就算没用上实例对象,也会进行实例化,这是要占据一定内存的。但在后面需要使用的时候,就不需要判断之类语句,所以非常快速。这就是以空间换时间。

你可能感兴趣的:(C++,c++,设计模式,单例模式)