设计模式之单例模式

曹旭辉  金牌会员 | 2024-6-22 06:35:51 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 475|帖子 475|积分 1425

何谓单例模式?

在一个项目中,全局范围内,某个类的实例有且仅有一个,通过这个唯一实例向其他模块提供数据的全局访问,这种模式就是单例模式。一个典范应用就是任务队列。
创建一个实例我们有如下方法:

  • 构造函数
  • 拷贝构造函数
  • 赋值构造函数
  • 移动构造函数
  • 移动赋值函数
我们只必要一个实例, 那么就要限制获取实例的途径

  • 将构造函数私有化,在类内部只调用一次。
  • 其他函数声明用 = delete 修饰
注意:


  • 在类外部不能调用构造函数,所以在类内部创建的这个唯一的对象必须是静态的,如许就可以通过类名来访问了,为了不破坏类的封装,把这个静态对象的访问权限设置为私有的
  • 在类中,静态成员函数才能访问其静态成员变量,所以给这个单例类提供一个静态函数用于得到这个静态的单例对象
单例模式的类的示例代码

因此,界说一个单例模式的类的示例代码如下:
  1. // 定义一个单例模式的类
  2. class Singleton {
  3. public:
  4.         Singleton(const Singleton& oth) = delete;
  5.         Singleton& operator=(const Singleton& oth) = delete;
  6.         Singleton(Singleton&& oth) = delete;
  7.         Singleton& operator=(Singleton&& oth) = delete;
  8.         static Singleton* get_instance();
  9. private:
  10.         Singleton() = default;
  11.         static Singleton* instance_;
  12. }
复制代码
饿汉模式

所谓饿汉模式是在类加载的时间立即进行实例化,如许就得到了唯一的实例。
注意:类的静态成员变量在利用之前必须在类的外部进行初始化
  1. class Singleton {
  2. public:
  3.         Singleton(const Singleton& oth) = delete;
  4.         Singleton& operator=(const Singleton& oth) = delete;
  5.         Singleton(Singleton&& oth) = delete;
  6.         Singleton& operator=(Singleton&& oth) = delete;
  7.         static Singleton* get_instance() {
  8.                 return instance_;
  9.         }
  10. private:
  11.         Singleton() = default;
  12.         static Singleton* instance_;
  13. }
  14. // 初始化
  15. Singleton* Singleton::instance_ = new Singleton;
  16. int main() {
  17.         ......
  18.         Singleton* ins = Singleton::get_instance();
  19.         ......
  20. }
复制代码
懒汉模式

懒汉模式一如其名“懒得不行”,在类加载的时间并不进行对象的实例化,而是比及必要的时间再进行实例化。这可能会引发线程安全问题!
下面这段代码在单线程下没有问题,但在多线程环境下将出现线程安全问题。
  1. class Singleton {
  2. public:
  3.         Singleton(const Singleton& oth) = delete;
  4.         Singleton& operator=(const Singleton& oth) = delete;
  5.         Singleton(Singleton&& oth) = delete;
  6.         Singleton& operator=(Singleton&& oth) = delete;
  7.         static Singleton* get_instance() {
  8.                 if (instance_ == nulllptr) {
  9.                         instance_ = new Singleton;
  10.                 }
  11.                 return instance_;
  12.         }
  13. private:
  14.         Singleton() = default;
  15.         static Singleton* instance_;
  16. }
  17. // 类加载时并不进行实例化
  18. Singleton* Singleton::instance_ = nullptr;
  19. int main() {
  20.         ......
  21.         // 此时进行实例化
  22.         Singleton* ins = Singleton::get_instance();
  23.         ......
  24. }
复制代码
试想在多线程环境下,同时有两个线程调用了 get_instance(),它们看到的instance_ 都是nullptr,那么就会创建出两个实例!怎么解决呢?
互斥锁保护

  1. class Singleton {
  2. public:
  3.         Singleton(const Singleton& oth) = delete;
  4.         Singleton& operator=(const Singleton& oth) = delete;
  5.         Singleton(Singleton&& oth) = delete;
  6.         Singleton& operator=(Singleton&& oth) = delete;
  7.         static Singleton* get_instance() {
  8.                 m_instance_.lock();
  9.                 if (instance_ == nulllptr) {
  10.                         instance_ = new Singleton;
  11.                 }
  12.                 m_instance_.unlock();
  13.                 return instance_;
  14.         }
  15. private:
  16.         Singleton() = default;
  17.         static Singleton* instance_;
  18.         static mutex m_instance_;
  19. }
  20. // 类加载时并不进行实例化
  21. Singleton* Singleton::instance_ = nullptr;
  22. mutex Singleton::m_instance_;
复制代码
这种简单利用互斥锁虽然可以解决线程安全问题,但是获取实例都必要上锁查抄(不仅第一次创建实例时必要上锁,纵然已经创建了实例仍然必要上锁查抄),效率实在低下!
双重查抄锁定

通过两个嵌套的if来判定单例对象是否为空的操作叫做双重查抄锁定
我们改进的思绪就是在加锁、解锁的代码块外层再添加一个判定,如许当任务队列的实例被创建出来之后,访问这个对象的线程就不会再实行加锁和解锁操作了。
  1. class Singleton {
  2. public:
  3.         Singleton(const Singleton& oth) = delete;
  4.         Singleton& operator=(const Singleton& oth) = delete;
  5.         Singleton(Singleton&& oth) = delete;
  6.         Singleton& operator=(Singleton&& oth) = delete;
  7.         static Singleton* get_instance() {
  8.                 if (instance_ == nulllptr) {
  9.                         m_instance_.lock();
  10.                         if (instance_ == nullptr) {
  11.                                 instance_ = new Singleton;
  12.                         }
  13.                         m_instance_.unlock();
  14.                 }
  15.                 return instance_;
  16.         }
  17. private:
  18.         Singleton() = default;
  19.         static Singleton* instance_;
  20.         static mutex m_instance_;
  21. }
  22. // 类加载时并不进行实例化
  23. Singleton* Singleton::instance_ = nullptr;
  24. mutex Singleton::m_instance_;
复制代码
万万没想到——可能会利用未创建好的实例?

到此仍然没有大功告成,因为万万没想到呆板指令会造成贫苦!
假设线程A实行到instance_ = new Singleton,线程B实行到第一个if (instance_ == nulllptr)。
instance_ = new Singleton在实行过程中对应的呆板指令可能会被重新排序。
正常过程如下:


  • 分配内存
  • 构造对象
  • 指针指向分配的内存
但是被重新排序以后实行顺序可能会酿成如许:


  • 分配内存
  • 指针指向分配的内存。
  • 构造对象
线程B在进行指针判定的时间instance_ 指针是不为空的,但这个指针指向的内存却没有被初始化!
怎么解决呢?利用atomic 原子操作(不可中断的操作)。
  1. class Singleton {
  2. public:
  3.         Singleton(const Singleton& oth) = delete;
  4.         Singleton& operator=(const Singleton& oth) = delete;
  5.         Singleton(Singleton&& oth) = delete;
  6.         Singleton& operator=(Singleton&& oth) = delete;
  7.         static Singleton* get_instance() {
  8.                 Singleton* obj = instance_.load();
  9.                 if (obj== nulllptr) {
  10.                         m_instance_.lock();
  11.                         obj = instance_.load();
  12.                         if (obj == nullptr) {
  13.                                 obj = new Singleton();
  14.                                 instance_.store(obj);
  15.                         }
  16.                         m_instance_.unlock();
  17.                 }
  18.                 return obj;
  19.         }
  20. private:
  21.         Singleton() = default;
  22.         static mutex m_instance_;
  23.         static atomic<Singleton*> instance_;
  24. }
  25. atomic<Singleton*> Singleton::instance_;
  26. mutex Singleton::m_instance_;
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

曹旭辉

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表