单例模式也叫单件模式。
Singleton是一个非常常用的设计模式,几乎所有稍微大一些的程序都会使用到它,所以构建一个线程安全并且 高效的Singleton很重要。
1. 单例类保证全局只有一个唯一实例对象。
2. 单例类提供获取这个唯一实例的接口。
设计单例模式有两种方式:
1、懒汉模式:
什么叫做懒汉模式呢?就是只有在第一次来调用的时候才会实例化这个对象。
简单的实现一个懒汉模式下的单例模式,先不考虑线程安全
#include<iostream>
#include<stdlib.h>
using namespace std;
class Singleton
{
public:
static Singleton*GetInstance()
{
if (_sInstance == NULL)
{
_sInstance = new Singleton();
}
return _sInstance;
}
private:
Singleton()
:_data(0)
{
}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
int _data;
static Singleton*_sInstance;
};
Singleton*Singleton::_sInstance = NULL;
2、但是如果要考虑到线程安全就必须加以改进,因为在多线程的情况下,那么线程1和线程2可能同时实例化出一个对象,或者线程1的时间片到了,但还没有创建晚,此时线程2进来了,于是线程2创建出了一个对象,之后线程1又回到之前创建的位置,在创建出一个对象,那么此时这个单例模式就是有问题的,因为它不是只创建了一个对象,而是多个,为了解决这个问题,我们必须要加锁
#include<iostream>
#include<stdlib.h>
#include<mutex>
using namespace std;
class Singleton
{
public:
static Singleton*GetInstance()
{
if (_sInstance == NULL)
{
_mutex.lock();
if (_sInstance == NULL)
{
_sInstance = new Singleton();
}
_mutex.unlock();
}
return _sInstance;
}
static void DelInstance()
{
if (_sInstance)
{
delete _sInstance;
_sInstance = NULL;
}
}
private:
Singleton()
:_data(0)
{
}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
int _data;
static Singleton*_sInstance;
static mutex _mutex;
};
Singleton*Singleton::_sInstance = NULL;
mutex Singleton::_mutex;
3、这样就解决了线程安全,但是同时引入了一个新的问题,在new的时候可能会抛异常,如果没有捕获那么就会发生死锁。因此为了避免死锁,我们应该引入RAII机制(资源获得即初始化,实际上是使其变成类对象。构造的时候加锁,析构的时候解锁)这样就避免了死锁,在这里我们可以直接使用c++11库里面std::lock_guard。
#include<iostream>
#include<stdlib.h>
#include<mutex>
using namespace std;
class Singleton
{
public:
static Singleton*GetInstance()
{
if (_sInstance == NULL)
{
std::lock_guard<mutex> lk(mutex);
if (_sInstance == NULL)
{
_sInstance = new Singleton();
}
}
return _sInstance;
}
static void DelInstance()
{
std::lock_guard<mutex> lk(mutex);
if (_sInstance)
{
delete _sInstance;
_sInstance = NULL;
}
}
private:
Singleton()
:_data(0)
{
}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
int _data;
static Singleton*_sInstance;
static mutex _mutex;
};
Singleton*Singleton::_sInstance = NULL;
mutex Singleton::_mutex;
在这里实现的都是双重检查,为什么是双重检查呢,就是为高效,避免每一次都需要加锁解锁,而只需要第一次创建对象的时候加锁。
4、由于我们的双重检查可能导致编译器优化
本来创建一个对象的步骤是 :分配空间–构造函数–赋值
由于编译器的优化可能会变成:分配空间–赋值–拷贝构造
于是这就会出现问题如果线程1完成了赋值之后,此时线程进来了,发现_sInstance不空,直接就返回了,那么它还没调用构造函数进行初始化,因此data就是一个随机值,那么就可能在使用data的时候出现问题。
为了解决这个问题我们引入了内存栅栏
以下加入内存栅栏进行处理,防止编译器重排栅栏后面的赋值
class Singleton
{
public:
static Singleton*GetInstance()
{
if (_sInstance == NULL)
{
std::lock_guard<mutex> lk(mutex);
if (_sInstance == NULL)
{
Singleton*temp = new Singleton();
MemoryBarrier();
_sInstance = temp;
}
}
return _sInstance;
}
static void DelInstance()
{
std::lock_guard<mutex> lk(mutex);
if (_sInstance)
{
delete _sInstance;
_sInstance = NULL;
}
}
private:
Singleton()
:_data(0)
{
}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
int _data;
static Singleton*_sInstance;
static mutex _mutex;
};
Singleton*Singleton::_sInstance = NULL;
mutex Singleton::_mutex;
恶汉模式
1、 指向实例的指针定义为静态私有,这样定义静态成员函数获取对象实例
class Singleton
{
public:
static Singleton*GetInstance()
{
assert(_sInstance);
return _sInstance;
}
static void DelInstance()
{
if (_sInstance)
{
delete _sInstance;
_sInstance = NULL;
}
}
private:
int _data;
static Singleton*_sInstance;
Singleton()
:_data(0)
{
}
Singleton(const Singleton&);
Singleton&operator()(const Singleton&);
};
Singleton*Singleton::_sInstance = new Singleton();
2、
class Singleton
{
public:
static Singleton*GetInstance()
{
static Singleton sInstance;
return &sInstance;
}
private:
int _data;
Singleton()
:_data(0)
{
}
Singleton(const Singleton&);
Singleton&operator()(const Singleton&);
};
还可以使用RAII技术实现回收机制
class Singleton
{
public:
static Singleton*GetInstance()
{
assert(_sInstance);
return _sInstance;
}
static void DelInstance()
{
std::lock_guard<mutex>lk(_mutex);
if (_sInstance)
{
delete _sInstance;
_sInstance = NULL;
}
}
class Gcc
{
public:
~Gcc()
{
cout << "DellInstance()" << endl;
DelInstance();
}
};
private:
int _data;
static Singleton*_sInstance;
static mutex _mutex;
Singleton()
:_data(0)
{
}
Singleton(const Singleton&);
Singleton&operator()(const Singleton&);
};
Singleton*Singleton::_sInstance = new Singleton();
Singleton::Gcc gc;// 使用RAII,定义全局的GC对象释放对象实例
mutex Singleton::_mutex;
小结:
**单例模式也叫单件模式
构建一个线程安全且高效的单例模式非常重要
1、单例保证全局只有一个唯一的实例
2、单例类提供一个获取这个唯一实例的接口
指向实例的指针定义为私有,这样定义的静态成员函数获取对象的实例
解决线程安全有两种模式:
1、懒汉模式:第一次来调用才会产生实例对象,用的时候再加载
加锁,有缺陷,缺陷在于效率。因为他只需要第一次来的时候加锁,后面来的时候不需要加锁,只是读
双重检查是为了高效
c++11提供了线程库,线程库里面提供了锁,两个线程需要有共同的锁
定义成为静态的成员变量
RAII机制防止死锁发生
资源获得及初始化
c++11支持锁的守卫,构造函加锁,析构函数解锁
2、恶汉模式:在之前就已经加载好,直接获取
在之前就已经初始化好,用它的时候直接返回
动态库的时候:但是如果在构造函数的时候创建线程,可能就出现一些问题
main函数以前只有一个线程是主线程
因此在主线程之前是线程安全的
直接定义一个静态的局部对象
返回它也是线程安全的
单例模式分为三个部分:
1、构造函数、拷贝构造、赋值运算符都定成私有的
、定义一个静态的接口函数
懒汉模式的缺陷:
内存栅栏:指防止编译器进行优化
在类理只能在栈上创建对象
将构造函数定义成私有的,在类里面去获取在栈上创建对象的接口,但是这种不好,有缺陷,定义成全局的AA去new 肯能就会编译通过
因此我们最好将new delete和new[] delete[]在类理声明称私有的,以防上述情况
**