一:概念:
设计模式就是一套被反复调用,经过分类,代码设计总结的经验:
单例模式:
也叫单间模式:Singletion是一种常用的设计模式,在大型的项目中都会使用,因为构建一个线程安全并且高效的单例模式很重要.
实现单例模式需要满足两个条件:
1:单例类保证全局只有唯一的实例对象
2:单例类提供获取这个唯一实例的接口
二:一个简单的单例
//实现一个简单的单例
**class Singletion
{
public:
//唯一对象的实例接口
static Singletion* GetInStance()
{
if (_Instance == NULL)
{
_Instance = new Singletion;//创建这个单例
}
return _Instance;
}
//删除实例对象
static void Destory()
{
if (_Instance)
{
delete _Instance;
_Instance = NULL;
}
}
void Print()
{
cout << _data << endl;
}
private:
//保证全局唯一实例对象,将构造函数设置为私有
Singletion()
:_data(0)
{}
static Singletion* _Instance;//通过静态成员函数来获取唯一对象的实例
//同时要防拷贝
Singletion(const Singletion&);
Singletion &operator =(const Singletion&);
int _data;
};
//静态成员函数需要在类外面初始化
Singletion *Singletion::_Instance = NULL;
int main()
{
Singletion::GetInStance()->Print();
Singletion::GetInStance()->Print();
Singletion::GetInStance()->Print();
system("pause");
return 0;
}**
但是这也只是满足单例的简单的功能,如果是多线程就会出现线程安全的问题这时候我们怎么解决呢?
三:两种模式
1:懒汉模式:–lazy load(懒加载,只有在用的时候创建)
这里面分三层去理解:
第一层:加锁,保证线程安全
class Singletion
{
public:
//获取唯一对象的接口
static Singletion *GetInstance()
{
//threads1线程1
//threads2线程2
//当线程来的时候加锁,当运行结束后调析构函数释放锁
_mus.lock();
if (_Instance == NULL)
{
_Instance = new Singletion;//为空时才会创建这个唯一的对象
}
_mus.unlock();
return _Instance;//返回这个唯一的对象
}
//删除对象的实例
static void Destory()
{
if (_Instance)
{
delete _Instance;
_Instance = NULL;
}
}
void Print()
{
cout << _data << endl;
}
private:
//构造函数定义成私有
Singletion()
:_data(0)
{}
Singletion(const Singletion&);
Singletion&operator =(const Singletion&);
//指向实例的对象定义成私有
static Singletion*_Instance ;
static mutex _mus;//类里面定义
int _data;
};
//类外面声明
Singletion* Singletion:: _Instance = NULL;
mutex Singletion::_mus;
void Test()
{
Singletion::GetInstance()->Print();
Singletion::GetInstance()->Print();
Singletion::GetInstance()->Print();
}
缺陷:
每次进来一个线程都要加锁,析构的时候都需要释放锁,这样会很麻烦,其实线程安全只要不违反多读多写就行,所以只需要给第一次来的线程加锁,后面不满足不为空的条件就不会,这样就可以满足多线程的线程安全.
但是会带来新的问题:死锁情况的发生,如果new对象失败会抛异常,线程会一直等待阻塞住,就出现所谓的死锁现象.
避免死锁我们怎么办呢?
改进:RAII(资源获得及初始化)
根据前面的智能指针的经验,我们通过一个类帮我们完成初始化,以及析构的事情,就算new 失败抛异常,程序结束也会掉构造函数
//RAII
class Lock
{
public:
Lock(mutex&mux)
:_mux(mux)
{
//在构造函数加锁
_mux.lock();
}
~Lock()
{
//在析构函数解锁
_mux.unlock();
}
private:
mutex&_mux;
//防拷贝
Lock(const Lock&);
Lock &operator =(const Lock&);
};
class Singletion
{
public:
//获取唯一对象的接口
static Singletion *GetInstance()
{
//threads1线程1
//threads2线程2
Lock lock(_mux);
if (_Instance == NULL)
{
_Instance = new Singletion;
}
Lock.unlock(_mux);
return _Instance;
}
//删除对象的实例
static void Destory()
{
if (_Instance)
{
delete _Instance;
_Instance = NULL;
}
}
void Print()
{
cout << _data << endl;
}
private:
//构造函数定义成私有
Singletion()
:_data(0)
{}
Singletion(const Singletion&);
Singletion&operator =(const Singletion&);
//指向实例的对象定义成私有
static Singletion*_Instance ;
static mutex _mux;//类里面定义
int _data;
};
//类外面声明
Singletion* Singletion:: _Instance = NULL;
mutex Singletion::_mux;
void Test()
{
Singletion::GetInstance()->Print();
Singletion::GetInstance()->Print();
Singletion::GetInstance()->Print();
}
int main()
{
Test();
system("pause");
return 0;
}
改进版:C++11库里面
在C++11提供了mutex互斥锁的概念,我们可以在#include里面查找.
可以调用库里面函数来完成:
class Singletion
{
public:
//获取唯一对象的接口
static Singletion *GetInstance()
{
//threads1线程1
//threads2线程2
std::lock_guard<std::mutex>lck(mutex);
if (_Instance == NULL)
{
_Instance = new Singletion;
}
return _Instance;
}
//删除对象的实例
static void Destory()
{
if (_Instance)
{
delete _Instance;
_Instance = NULL;
}
}
void Print()
{
cout << _data << endl;
}
private:
//构造函数定义成私有
Singletion()
:_data(0)
{}
Singletion(const Singletion&);
Singletion&operator =(const Singletion&);
//指向实例的对象定义成私有
static Singletion*_Instance ;
static mutex _mux;//类里面定义
int _data;
};
//类外面声明
Singletion* Singletion:: _Instance = NULL;
mutex Singletion::_mux;
void Test()
{
Singletion::GetInstance()->Print();
Singletion::GetInstance()->Print();
Singletion::GetInstance()->Print();
}
int main()
{
Test();
system("pause");
return 0;
}
RAII虽然解决了线程安全的问题,但是会带来不够高效
第二层:双重检查实现高效性:
class Singletion
{
public:
//获取唯一对象的接口
static Singletion *GetInstance()
{
//threads1线程1
//threads2线程2
//在linus下是lock和unlock在windows下也有C++11 mutex中
//第二层双重检查保证,效率
if (_Instance == NULL)//双重检查体现高效性
{
//Lock lock(_mux);
std::lock_guard<std::mutex>lck(mutex);
if (_Instance == NULL)
{
//第一层加锁:这个可以解决线程安全的问题,但是不满足高效这个条件(只需要第一次加锁,后面的不需要不为空只是读操作)
_Instance = new Singletion;
}
}
return _Instance;
}
//删除对象的实例
static void Destory()
{
if (_Instance)
{
delete _Instance;
_Instance = NULL;
}
}
void Print()
{
cout << _data << endl;
}
private:
//构造函数定义成私有
Singletion()
:_data(0)
{}
Singletion(const Singletion&);
Singletion&operator =(const Singletion&);
//指向实例的对象定义成私有
static Singletion*_Instance ;
static mutex _mux;//类里面定义
int _data;
};
//类外面声明
Singletion* Singletion:: _Instance = NULL;
mutex Singletion::_mux;
void Test()
{
Singletion::GetInstance()->Print();
Singletion::GetInstance()->Print();
Singletion::GetInstance()->Print();
}
int main()
{
Test();
system("pause");
return 0;
}
实现了高效性,但是可能会出现CPU的某些优化,从而导致执行顺序被打乱
第三层:内存栅栏
class Singletion
{
public:
//获取唯一对象的接口
static Singletion *GetInstance()
{
//threads1线程1
//threads2线程2
//在linus下是lock和unlock在windows下也有C++11 mutex中
//第二层双重检查保证,效率
if (_Instance == NULL)
{
//Lock lock(_mux);
std::lock_guard<std::mutex>lck(mutex);
if (_Instance == NULL)
{
//第一层加锁:这个可以解决线程安全的问题,但是不满足高效这个条件(只需要第一次加锁,后面的不需要不为空只是读操作)
//改进加互斥锁
//std::lock_guard<std::mutex>lck(mutex);// RAII(资源分配即初始化)在构造函数自动加锁,在析构函数自动
//_Instance = new Singletion;
//这里面需要做三件事:1分配空间2:调构造函数3:赋值
//编译器可能会优化,将2和3重排,这样就会导致其他线程获取到调用未构造函数初始化的对象可能会出现随机值的情况
//加入内存栅栏主要是防止编译器重排后面的赋值
Singletion *tmp = new Singletion();
MemoryBarrier();//内存栅栏
_Instance = tmp;
}
}
return _Instance;
}
//删除对象的实例
static void Destory()
{
if (_Instance)
{
delete _Instance;
_Instance = NULL;
}
}
void Print()
{
cout << _data << endl;
}
private:
//构造函数定义成私有
Singletion()
:_data(0)
{}
Singletion(const Singletion&);
Singletion&operator =(const Singletion&);
//指向实例的对象定义成私有
static Singletion*_Instance ;
static mutex _mux;//类里面定义
int _data;
};
//类外面声明
Singletion* Singletion:: _Instance = NULL;
mutex Singletion::_mux;
void Test()
{
Singletion::GetInstance()->Print();
Singletion::GetInstance()->Print();
Singletion::GetInstance()->Print();
}
int main()
{
Test();
system("pause");
return 0;
}
加内存栅栏的情况是为了防止cpu的优化.
2:饿汉模式:
简单,高效,不用加锁,但是在某些场景会有缺陷
第一种:
*class Singletion
{
public:
static Singletion*GetInstance()
{
static Singletion _Instance;
return &_Instance;
}
void Print()
{
cout << _data << endl;
}
private:
Singletion()
:_data(0)
{}
Singletion(const Singletion&);
Singletion&operator =(const Singletion&);
int _data;
};
void Test()
{
Singletion::GetInstance()->Print();
Singletion::GetInstance()->Print();
Singletion::GetInstance()->Print();
}
int main()
{
Test();
system("pause");
return 0;
}*
第二种:
``class singletion
{
public:
static singletion&getinstance()
{
assert(_instance);//保证不会创建对象
return *_instance;//返回对象
}
//删除单例
static void destory()
{
//这样就保证多线程来的时候也能正常的释放
std::lock_guard<std::mutex>lck(_mux); // raii(资源分配即初始化)在构造函数自动加锁,在析构函数自动
if (_instance)
{
delete _instance;
_instance = NULL;
}
}
void print()
{
cout << _data << endl;
}
//内置一个gc类,只有调出了作用域才会调用析构函数,保证了下次获取还是唯一对象
struct gc
{
~gc()
{
cout << "destory()" << endl;
destory();
}
};
private:
//构建只有唯一对象的实例
singletion()
:_data(0)
{}
//防拷贝
singletion(const singletion&);
singletion &operator = (const singletion&);
static singletion*_instance ;//声明
static mutex _mux;//声明
int _data;
};
singletion *singletion::_instance = new singletion;
mutex singletion::_mux;//定义成缺省值
void test()
{
singletion::getinstance().print();
singletion::getinstance().print();
singletion::getinstance().print();
singletion::getinstance().print();
//如何调单例ide释放函数
//atexit singletion::destory();
}
int main()
{
test();
system("pause");
return 0;
}``
注意:在单例模式中一般不会轻易去删除这个单例对象,但是在某些场景下又不的不这样做,如:文件锁,文件句柄,数据库中.这些随着程序的关闭不会立即关闭资源,必须在程序关闭之前进行手动名关闭.我们可以模仿前面的RAII定义一个内置类帮我们完成例对象的释放.
四:总结:
两个特征
1:保证全局具有唯一实例的对象
2:提供换取这个唯一对象的接口
两种模式:
一:懒汉模式
1:简单的单例,会有线程安全的问题,–解决加锁
加锁又会导致新的问题:死锁解决–RAII
2:线程安全但是不够高效–双重检查(保证效率)
3:可能会出现CPU的优化,导致指令重排–内存栅栏
二:饿汉模式:简单,高效,但是某些场景有缺陷.