一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式有两种实现模式:
饿汉模式
不管将来用不用,程序启动时(main函数之前)就创建一个唯一的实例对象
- 优点:实现简单
- 缺点:若程序中单例较多,可能会导致进程启动慢;且如果有多个单例类对象实例启动顺序不确定。
// 饿汉模式
class Singleton
{
public:
static Singleton& GetInstance()
{
return _sInst;
}
private:
// 构造函数私有化
Singleton(){ }
// 拷贝构造函数封掉
Singleton(const Singleton&) = delete;
static Singleton _sInst;
};
Singleton Singleton::_sInst; // main函数之前就创建初始化了这个实例对象--对象在静态区
如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好
懒汉模式
最后一刻(第一次调用GetInstance时)才创建对象
如果单例对象构造十分耗时或者占用很多资源,比如加载插件、 初始化网络连接、读取文件等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好
- 优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制
- 缺点:实现稍复杂
class Singleton
{
public:
static Singleton& GetInstance()
{
/*
1.存在线程安全问题
if (_spInst == nullptr)
{
_spInst = new Singleton;
}
return *_spInst;
*/
/*
2.解决了线程安全问题,但第一个线程创建好了实例之后,后面的线程进来虽然不创建实例,仍然需要加锁解锁
_smtx.lock(); // 加锁
if (_spInst == nullptr)
{
_spInst = new Singleton;
}
_smtx.unlock(); // 解锁
return *_spInst;
*/
// 饿汉模式有线程安全问题,需要加锁
// 3.双检查解决锁的效率问题
if (_spInst == nullptr) // 解决效率问题,后面的线程发现已有实例就不进去,防止加锁解锁
{
_smtx.lock(); // 加锁
if (_spInst == nullptr) // 解决线程安全问题
{
_spInst = new Singleton;
}
_smtx.unlock();
}
return *_spInst;
}
private:
// 构造函数私有化
Singleton(){ }
// 拷贝构造函数封掉
Singleton(const Singleton&) = delete;
static Singleton* _spInst;
static mutex _smtx;
};
Singleton* Singleton::_spInst = nullptr; // main函数之前就创建初始化指针
mutex Singleton::_smtx;