什么是单例模式
在系统中保证一个类只存在唯一实例的模式即为单例模式。如日志器、数据库等。
实现单例模式
单例模式一般分为两种模式:懒汉式、饿汉式。
- 饿汉式:像一个很饿的人,在程序启动时就完成了加载,在整个程序运行过程中一直存在。
- 懒汉式:像一个很懒的人,只有在需要使用的时候,动态的加载。
两种模式的优缺点:
模式 | 优点 | 缺点 |
---|---|---|
饿汉式 | 程序运行过程中不需要重新加载,执行效率更快 | 不管是否使用,都会占用内存 |
懒汉式 | 只有使用到的时候才会占用内存 | 程序运行过程中加载,若加载时间较长,影响程序流程 |
单例模式的设计要点:
- 构造函数私有化
- 对外提供一个接口获取实例
- 只有一个对象实例
饿汉式
使用静态类成员实现,在成员启动时完成初始化
// 饿汉式,使用静态类成员实现,在成员启动时完成初始化
class SingleTonTest1{
private:
SingleTonTest1()=default; // 构造函数私有化
public:
// 提供接口获取实例对象
static SingleTonTest1* getInstance(){
return singleTonTest1.get();
}
private:
static std::unique_ptr<SingleTonTest1> singleTonTest1;
};
std::unique_ptr<SingleTonTest1> SingleTonTest1::singleTonTest1 (new SingleTonTest1); // 使用静态类成员,在程序启动时完成初始化
懒汉式
懒汉式一般又三种实现方式: 非线程安全、线程安全、双重检查(推荐)
实例初始化流程如下:
非线程安全
在第一次获取实例时检查实例是否初始化,若未初始化,则将实例初始化。因在检查实例时未加入互斥锁,所以在第一次初始化实例对象时,可能会有多个线程完成实例的初始化,造成内存泄漏。
实现代码如下:
// 懒汉式,非线程安全
class SingleTonTest2{
private:
SingleTonTest2()=default;// 构造函数私有化
public:
// 提供接口获取实例对象
static SingleTonTest2* getInstance(){
// 检查实例是否完成初始化
if(nullptr == singleTonTest2) // 可能会有多个线程同时检查到实例未初始化,非线程安全
{
// 初始化对象
singleTonTest2.reset(new SingleTonTest2);
return singleTonTest2.get();
}
// 返回对象
return singleTonTest2.get();
}
private:
static std::unique_ptr<SingleTonTest2> singleTonTest2;
};
std::unique_ptr<SingleTonTest2> SingleTonTest2::singleTonTest2 (nullptr); // NOLINT
线程安全(不推荐)
在非线程安全的基础上,加锁可以解决多线程造成的多次初始化的问题。这种方式主要问题是不管实例是否已完成了初始化,都需要先加锁,会影响程序运行效率。
// 懒汉式,线程安全
class SingletonTest3{
private:
SingletonTest3()=default;// 构造函数私有化
public:
// 提供接口获取实例对象
static SingletonTest3* getInstance() {
lock.lock(); // 每次检查前,先加互斥锁
if (nullptr == singletonTest3) {
singletonTest3.reset(new SingletonTest3);
}
lock.unloc();
// 返回实例
return singletonTest3.get();
}
private:
static std::unique_ptr<SingletonTest3> singletonTest3;
static std::mutex lock;
};
std::unique_ptr<SingletonTest3> SingletonTest3::singletonTest3 (nullptr); // NOLINT
std::mutex SingletonTest3::lock;
双重检查(推荐)
在线程安全的基础上,我们把检查实例的判断先提出来,在准备初始化前先加锁,再判断一次实例是否已经初始化,这样既保证运行效率又保证了线程安全。
// 懒汉式,双重检查
class SingletonTest4{
private:
SingletonTest4()=default;// 构造函数私有化
public:
// 提供接口获取实例对象
static SingletonTest4* getInstance(){
if(nullptr == singletonTest4) { // 第一次判断实例是否初始化
lock.lock(); // 加入互斥锁
// 第二次判断实例是否初始化,如果已被初始化了,则跳过初始化流程
if (nullptr == singletonTest4) {
singletonTest4.reset(new SingletonTest4);
}
lock.unlock();
}
return singletonTest4.get();
}
private:
static std::unique_ptr<SingletonTest4> singletonTest4;
static std::mutex lock;
};
std::unique_ptr<SingletonTest4> SingletonTest4::singletonTest4 (nullptr); // NOLINT
std::mutex SingletonTest4::lock;