IT评测·应用市场-qidao123.com技术社区

标题: 怎样优雅地实现全局唯一?深入理解单例模式 [打印本页]

作者: 麻花痒    时间: 4 天前
标题: 怎样优雅地实现全局唯一?深入理解单例模式
怎样优雅地实现全局唯一?深入理解单例模式

一、什么是单例模式?

单例模式是一种创建型设计模式,旨在确保一个类只有一个实例,并为该实例提供全局访问点,从而制止全局变量的命名污染,并支持耽误初始化Wikipedia。
关键点:

二、C++实现示例

  1. #include<iostream>
  2. using namespace std;
  3. #if 0
  4. // 饿汉模式 -> 定义类的时候创建单例对象
  5. // 在多线程的场景下没用线程安全问题
  6. // 线程安全:多线程同时访问单例模式
  7. // 定义一个单例模式的任务队列
  8. class TaskQueue
  9. {
  10. public:
  11.     TaskQueue(const TaskQueue & t) = delete;
  12.     TaskQueue& operator =(const TaskQueue& t) = delete;
  13.     static TaskQueue *getInstance()
  14.     {
  15.         return m_taskQ;
  16.     }
  17.     void print()
  18.     {
  19.         cout<<"我是单例对象的一个成员函数..."<<endl;
  20.     }
  21. private:
  22.     TaskQueue() = default;
  23.     // 只能通过类名访问静态成员属性或方法
  24.     static TaskQueue* m_taskQ;
  25. };
  26. TaskQueue* TaskQueue::m_taskQ = new TaskQueue;
  27. #endif
  28. #if 1
  29. // 懒汉模式 -> 什么时候使用这个单例,再使用的时候再去创建对应的实例
  30. // 在多线程的场景下可能存在线程安全问题
  31. // 加互斥锁,让线程依次访问单例对象
  32. // 比较节省内存空间
  33. class TaskQueue
  34. {
  35. public:
  36.     TaskQueue(const TaskQueue & t) = delete;
  37.     TaskQueue& operator =(const TaskQueue& t) = delete;
  38.     static TaskQueue *getInstance()
  39.     {
  40.         if(m_taskQ == nullptr)
  41.         {
  42.             m_taskQ = new TaskQueue;
  43.         }
  44.         return m_taskQ;
  45.     }
  46.     void print()
  47.     {
  48.         cout<<"我是单例对象的一个成员函数..."<<endl;
  49.     }
  50. private:
  51.     TaskQueue() = default;
  52.     //只能通过类名访问静态成员属性或方法
  53.     static TaskQueue* m_taskQ;
  54. };
  55. TaskQueue* TaskQueue::m_taskQ = nullptr;
  56. #endif
  57. int main()
  58. {
  59.     TaskQueue* taskQ = TaskQueue::getInstance();
  60.     taskQ->print();
  61.     return 0;
  62. }
复制代码
懒汉模式利用双重查抄锁定解决线程安全问题
问题原因:
多线程调用懒汉模式getInstance(),就会创建出多个TaskQueue的实例,违背单例模式的界说,所谓的单例就是只能有唯一的一个单例对象
解决方法:
1、互斥锁解决线程安全问题
互斥锁:制止同时访问,按顺序依次访问
利用原子变量解决双重查抄的问题:
  1. //互斥锁头文件
  2. #include<mutex>
  3. ...
  4.    mutex TaskQueue::m_mutex;
  5. ...
  6.    static mutex m_mutex;
  7. ...
  8.    static TaskQueue *getInstance()
  9.     {
  10.         if(m_taskQ == nullptr)//第一次检查
  11.         {
  12.             //进行加锁操作
  13.             m_mutex.lock();
  14.             if(m_taskQ == nullptr)//第二次检查
  15.             {
  16.                 m_taskQ = new TaskQueue;
  17.             }
  18.             //进行解锁操作 *注意*一个程序加锁之后一定要解锁,否则导致死锁
  19.             m_mutex.unlock();
  20.         }
  21.         return m_taskQ;
  22.     }
  23. ...
复制代码
利用原子变量解决双重查抄的问题
  1. //原子变量头文件
  2. #include<atomic>
  3. ...
  4.    atomic<TaskQueue*> TaskQueue::m_taskQ = nullptr; //初始化
  5. ...
  6.    static atomic<TaskQueue*>m_taskQ;
  7. ...
  8.    static TaskQueue *getInstance()
  9.     {
  10.         TaskQueue* task = m_taskQ.load();
  11.         if(task == nullptr)
  12.         {
  13.             m_mutex.lock();
  14.             task = m_taskQ.load();//通过原子变量加载实例化指针
  15.             if(task == nullptr)
  16.             {
  17.                 task = new TaskQueue;
  18.                 m_taskQ.store(task);
  19.             }
  20.             m_mutex.unlock();
  21.         }
  22.         return task;
  23.     }
  24. ...
复制代码
2、局部静态对象解决线程安全问题
  1. // 使用静态的局部对象解决线程安全问题 ->编译器支持C++11
  2. ...
  3. class TaskQueue
  4. {
  5. public:
  6.     TaskQueue(const TaskQueue & t) = delete;
  7.     TaskQueue& operator =(const TaskQueue& t) = delete;
  8.     static TaskQueue *getInstance()
  9.     {
  10.         static TaskQueue task;
  11.         return &task;
  12.     }
  13.     void print()
  14.     {
  15.         cout<<"我是单例对象的一个成员函数..."<<endl;
  16.     }
  17. private:
  18.     TaskQueue() = default;
  19. };
  20. ...
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/) Powered by Discuz! X3.4