C++:05.单例模式

单例模式

一个类只能被实例化一次,产生一个对象。

在类中,要构造一个实例,就需要通过构造函数,所以为了防止在外部调用类的构造函数而构造实例,需要将构造函数的访问权限标记为protected或private;

并且需要提供给全局访问点,就需要在类中定义一个static函数,返回在类内部唯一构造的实例。

懒汉式:在第一次用到类实例的时候才会去实例化,访问量较小时,采用懒汉式,以时间换空间。并不安全,因为实例化并不是原子操作,可能在实例化的过程中,在给指针赋值之前,cpu切换线程导致产生多个对象。

饿汉式:在单例类定义的时候就进行实例化。访问量较大、线程较多时,采用饿汉式,以空间换时间。线程安全,因为一开始就实例化了。

懒汉式:
class Single           
{                            
public:
	static pthread_mutex_t mtx;   互斥锁

***************在饿汉式中该函数只需要一个return***************
	static Single* get_Single()
	{
		if (p == NULL)  双重检查
		{
			pthread_mutex_lock(&mtx);
			if (p == NULL)  多线程,需要加锁,保证其他线程无法获取锁而阻塞。
                                        如果不加锁,又因为实例化不是原子操作,导致可能有多个进程创建多个对象
			{
				p = new Single;  如果还没有唯一的对象就先生成,实例化,不是原子操作
			}
			pthread_mutex_unlock(&mtx);
		}
		return p;
	}
private:
	static Single* volatile p;
	Single(){}
};
pthread_mutex_t Single::mtx = PTHREAD_MUTEX_INITIALIZER;  创建互斥锁

***************懒汉与饿汉的区别就在于这个静态成员变量实例化的位置不一样***************
Single* Single::p = NULL;  静态成员变量初始化       懒汉式在此处实例化
 
int main()
{
	Single* tmp = Single::get_Single();  用作用域加成员函数名调用静态函数。
	Single* tmp1 = Single::get_Single(); 
	cout <<tmp <<endl<<tmp1<<endl;
	return 0;
}

解决几个问题:

1、为什么用静态函数,静态成员变量?

调用类里的函数需要先构造对象,有对象才能调用类内成员方法。但这个方法肯定不适合单例模式。而另一种方法就是静态函数,可以使用类名直接调用。而静态函数只能调用静态成员方法。

2、为什么加双重检查?

咱们从内向外分析,首先为了满足单例模式的要求,也就是说p创建了对象,之后就不能再创建了,外面就需要加一层判断,保证在单线程的情况下可以顺利实现单例。但这样也仅仅在单线程下满足,如果多线程,由于CPU时间片轮转,很可能导致在赋给p途中被截断,从而另一个进程也创建了对象,为避免这种情况的发生,需要在外面加一互斥锁。但加锁的操作是由内核完成的,也就是说每次检查是否持锁都需要陷入内核。这种情况我们发现对於单线程又发生了不友好,单线程没有必要取锁解锁消耗时间和资源。所以我们加了最外面的一层判断,如果p已经不是NULL,我们直接跳过,不再进行锁操作。

3、私有静态成员变量volatile是个啥?

volatile:

1、防止多线程对共享变量进行线程缓存操作,一个线程修改了共享变量,另一个线程不能及时看到。

解释一下:除了cpu寄存器上的空间,和内存,在介于两者之间,还存在cache缓存区,多线程共享变量,在线程栈上缓存一份数据的拷贝,导致,cpu在取第二个指令的时候。误认为p指针还没有实例化。

2、防止编译器对涉及value操作的代码进行指令重排序

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章