什么是单例模式
单例模式是设计模式的最简单的形式之一。这个模式的最主要目的是使得类的一个对象称为系统中唯一的实例。
单例模式的特点
一个类只有一个对象。
例如:一夫一妻制。
单例模式的分类
- 饿汉模式:在创建对象的时候,直接分配资源,当对象想要使用资源的时候,立即可以使用。
- 懒汉模式:在创建对象时不分配资源,当该对象需要资源时,再分配资源。
例如:我们在逛超市的时候,有可能会买好几天的东西放在冰箱里,这样当我们需要的时候直接从冰箱里拿就可以,这就属于饿汉模式。
当我们需要的时候,再去超市买,这就是懒汉模式。
我们可以考虑一下:我们在打游戏的时候,由于游戏资源非常的大,所以不可能一次性将所有的游戏数据加载到CPU上,有种“延时加载”的思想(懒汉模式)。反之,当在启动时,将所需的资源准备好的,属于“及时加载”的思想(饿汉模式)
饿汉模式
饿汉模式怎么实现创建对象时,立即分配资源呢?
用static关键字实现。
static关键字讲解
template<class T>
class Single_Starve
{
public:
static T *GetData()
{
return &_data;
}
private:
static T _data;
};
饿汉模式是线程安全的,在类的创建时同时实例化一个静态的实例。
懒汉模式
由于懒汉模式是在实际使用资源的时候才会主动申请资源。
template<class T>
class Singel_Slacker
{
public:
static T *GetData()
{
_mutex.lock(); //加锁操作
if(_data == NULL)
_data = new T();
_mutex.unlock(); //解锁操作
return _data;
}
private:
volatile static T* _data; //这里只是申请了保存资源地址的空间
static std::mutex _mutex; //调用mutex库函数,定义锁资源
};
volatile关键字:防止编译器过度优化。
担心编译器对代码进行优化,使得数据直接从寄存器中取,会造成数据逻辑混乱,所以使用volatile关键字。
由于懒汉模式是在使用资源的时候,才申请资源,所以会出现线程安全问题,因此在申请资源的时候,需要进行加锁操作,申请完资源后,需要解锁。
但是上面代码有锁冲突的概率
那什么是锁冲突
当_data = NULL时,A线程运行时,先进行加锁操作,这时B线程也过来了,等待锁资源,当A操作完成后,现在_data 已经不为NULL,A释放锁资源,这时处于等待队列中的线程B进行加锁操作,加完锁后发现_data已经不为NULL,则返回A申请的资源后才解锁,这样无用的操作,就会降低效率。
template<class T>
class Singel_Slacker
{
public:
static T *GetData()
{
if(_data == NULL) //双判断,降低锁冲突
{
_mutex.lock(); //加锁操作
if(_data == NULL)
_data = new T();
_mutex.unlock(); //解锁操作
}
return _data;
}
private:
volatile static T* _data; //这里只是申请了保存资源地址的空间
static std::mutex _mutex; //调用mutex库函数,定义锁资源
};
懒汉模式是延迟加载思想,面对多线程访问的时候,需要进行加锁操作,实现同步,为了增加效率,降低锁冲突,则需要进行二次判断