单例模式
一个类只能被实例化一次,产生一个对象。
在类中,要构造一个实例,就需要通过构造函数,所以为了防止在外部调用类的构造函数而构造实例,需要将构造函数的访问权限标记为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操作的代码进行指令重排序