单例模式
- 何谓单例模式?
- 单例模式的类的示例代码
- 饿汉模式
- 懒汉模式
- 互斥锁保护
- 双重检查锁定
- 万万没想到——可能会使用未创建好的实例?
何谓单例模式?
在一个项目中,全局范围内,某个类的实例有且仅有一个,通过这个唯一实例向其他模块提供数据的全局访问,这种模式就是单例模式。一个典型应用就是任务队列。
创建一个实例我们有如下方法:
- 构造函数
- 拷贝构造函数
- 赋值构造函数
- 移动构造函数
- 移动赋值函数
我们只需要一个实例, 那么就要限制获取实例的途径:
- 将构造函数私有化,在类内部只调用一次。
- 其他函数声明用
= delete
修饰
注意:
- 在类外部不能调用构造函数,所以在类内部创建的这个唯一的对象必须是静态的,这样就可以通过类名来访问了,为了不破坏类的封装,把这个静态对象的访问权限设置为私有的
- 在类中,静态成员函数才能访问其静态成员变量,所以给这个单例类提供一个静态函数用于得到这个静态的单例对象
单例模式的类的示例代码
因此,定义一个单例模式的类的示例代码如下:
// 定义一个单例模式的类
class Singleton {
public:Singleton(const Singleton& oth) = delete;Singleton& operator=(const Singleton& oth) = delete;Singleton(Singleton&& oth) = delete;Singleton& operator=(Singleton&& oth) = delete;static Singleton* get_instance();
private:Singleton() = default;static Singleton* instance_;
}
饿汉模式
所谓饿汉模式是在类加载的时候立即进行实例化,这样就得到了唯一的实例。
注意:类的静态成员变量在使用之前必须在类的外部进行初始化。
class Singleton {
public:Singleton(const Singleton& oth) = delete;Singleton& operator=(const Singleton& oth) = delete;Singleton(Singleton&& oth) = delete;Singleton& operator=(Singleton&& oth) = delete;static Singleton* get_instance() {return instance_;}
private:Singleton() = default;static Singleton* instance_;
}// 初始化
Singleton* Singleton::instance_ = new Singleton;int main() {......Singleton* ins = Singleton::get_instance();......
}
懒汉模式
懒汉模式一如其名“懒得不行”,在类加载的时候并不进行对象的实例化,而是等到需要的时候再进行实例化。这可能会引发线程安全问题!
下面这段代码在单线程下没有问题,但在多线程环境下将出现线程安全问题。
class Singleton {
public:Singleton(const Singleton& oth) = delete;Singleton& operator=(const Singleton& oth) = delete;Singleton(Singleton&& oth) = delete;Singleton& operator=(Singleton&& oth) = delete;static Singleton* get_instance() {if (instance_ == nulllptr) {instance_ = new Singleton;}return instance_;}
private:Singleton() = default;static Singleton* instance_;
}
// 类加载时并不进行实例化
Singleton* Singleton::instance_ = nullptr;int main() {......// 此时进行实例化Singleton* ins = Singleton::get_instance();......
}
试想在多线程环境下,同时有两个线程调用了 get_instance()
,它们看到的instance_
都是nullptr
,那么就会创建出两个实例!怎么解决呢?
互斥锁保护
class Singleton {
public:Singleton(const Singleton& oth) = delete;Singleton& operator=(const Singleton& oth) = delete;Singleton(Singleton&& oth) = delete;Singleton& operator=(Singleton&& oth) = delete;static Singleton* get_instance() {m_instance_.lock();if (instance_ == nulllptr) {instance_ = new Singleton;}m_instance_.unlock();return instance_;}
private:Singleton() = default;static Singleton* instance_;static mutex m_instance_;
}
// 类加载时并不进行实例化
Singleton* Singleton::instance_ = nullptr;
mutex Singleton::m_instance_;
这种简单使用互斥锁虽然可以解决线程安全问题,但是获取实例都需要上锁检查(不仅第一次创建实例时需要上锁,即使已经创建了实例仍然需要上锁检查),效率实在低下!
双重检查锁定
通过两个嵌套的if
来判断单例对象是否为空的操作叫做双重检查锁定
我们改进的思路就是在加锁、解锁的代码块外层再添加一个判断,这样当任务队列的实例被创建出来之后,访问这个对象的线程就不会再执行加锁和解锁操作了。
class Singleton {
public:Singleton(const Singleton& oth) = delete;Singleton& operator=(const Singleton& oth) = delete;Singleton(Singleton&& oth) = delete;Singleton& operator=(Singleton&& oth) = delete;static Singleton* get_instance() {if (instance_ == nulllptr) {m_instance_.lock();if (instance_ == nullptr) {instance_ = new Singleton;}m_instance_.unlock();}return instance_;}
private:Singleton() = default;static Singleton* instance_;static mutex m_instance_;
}
// 类加载时并不进行实例化
Singleton* Singleton::instance_ = nullptr;
mutex Singleton::m_instance_;
万万没想到——可能会使用未创建好的实例?
到此仍然没有大功告成,因为万万没想到机器指令会造成麻烦!
假设线程A执行到instance_ = new Singleton
,线程B执行到第一个if (instance_ == nulllptr)
。
instance_ = new Singleton
在执行过程中对应的机器指令可能会被重新排序。
正常过程如下:
- 分配内存
- 构造对象
- 指针指向分配的内存
但是被重新排序以后执行顺序可能会变成这样:
- 分配内存
- 指针指向分配的内存。
- 构造对象
线程B在进行指针判断的时候instance_
指针是不为空的,但这个指针指向的内存却没有被初始化!
怎么解决呢?使用atomic
原子操作(不可中断的操作)。
class Singleton {
public:Singleton(const Singleton& oth) = delete;Singleton& operator=(const Singleton& oth) = delete;Singleton(Singleton&& oth) = delete;Singleton& operator=(Singleton&& oth) = delete;static Singleton* get_instance() {Singleton* obj = instance_.load();if (obj== nulllptr) {m_instance_.lock();obj = instance_.load();if (obj == nullptr) {obj = new Singleton();instance_.store(obj);}m_instance_.unlock();}return obj;}
private:Singleton() = default;static mutex m_instance_;static atomic<Singleton*> instance_;
}
atomic<Singleton*> Singleton::instance_;
mutex Singleton::m_instance_;