zoukankan      html  css  js  c++  java
  • 单例模式 【饿汉式、懒汉式、线程安全、单例资源释放】

    单例模式,保证整个工程中,有且仅有一个该类的实例对象。

    一、饿汉式单例

    二、懒汉式单例

    三、创建单例的线程安全

       多线程情景中创建单例, 单例类的静态单例对象数据成员此时作为共享数据,那么势必有必要保证 获取单例的函数是线程安全的。 这里通过使用C++11新标准版本的线程库函数,来完成对线程安全的单例获取函数的编写。分两步改造,一个改版,以及一句总结 分享给大家。

    ///< Single.h-----------------------------
    
    //仅仅针对 static  Single * instance() 函数的编写, 其他的忽略, 请阅读其他小节内容。
    
    //线程安全, 保护共享数据,毫无疑问,应采用 “加锁” , 这里使用 互斥量。
    
    ======  第一版  ======
    public:
    static  Single * instance() {
           std::unique_lock<std::mutex>  guardLock ( m_mutex);   ///< 【核心点1】确保同一时刻,仅有一个线程能够执行
           if  (m_instance  ==  nullptr) {
                  m_instance  =  new Single();
                  static AutoClearSingle  autoClearSingle;
           }
            
           return m_instance;    
    }
    
    private:
           std::mutex  m_mutex;    
    
    第一版问题点: 每次获取单例对象, 都需要等待锁, 而实际上,m_instance 为 nullptr,仅第一次获取单例对象时成立。 假如线程很多, 获取单例对象的调用次数频繁,这将极大的降低代码的执行效率。
    
    ======  第二版  ======
    public:
    static  Single * instance() {
           if  (m_instance  ==  nullptr) {   ///<【核心点1】  双重检查  (又叫:双重锁定)
    
                  std::unique_lock<std::mutex>  guardLock ( m_mutex);    ///<【核心点2】
                  if  (m_instance  ==  nullptr) {
                         m_instance  =  new Single();
                         static AutoClearSingle  autoClearSingle;
                  }
           
           }
    
            
           return m_instance;    
    }
    
    private:
           std::mutex  m_mutex;  
    
    解析: 多出的第一个判断  if  (m_instance  ==  nullptr)
    如果  m_instance  !=  nullptr , 条件成立, 则肯定 m_instance 被 new 过了 。
    如果  m_instance  == nullptr , 不一定代表, m_instance一定没被 new  过。 因此需要上锁。
    简简单单的多一行双重检查机制, 能够大大的提高效率, 同时有能保证线程安全。
    
    
    ======  改版  ======
    public:
           static  Single * instance() {
                  std::call_once (m_call_once_flag,  createInstance);   ///< 【核心点1】
                  return m_instance;
           }
    
    private:
           static  void  createInstance() {
                  m_instance  =  new  Single();
                  static  AutoClearSingle  autoClearSingle;
           }
    
           std::once_flag  m_call_once_flag;    ///< 【核心点2】 标记 std::call_once 传入的可调用对象,是否被调用过。
    
    备注:createInstance函数可以使用  lambda  代替, 让代码更简洁。
    解析:
    std::call_once 是 C++11 引入的新函数。 能够保证传入的可调用对象,只被调用一次。std::call_once 具备互斥量的能力, 而且传言在效率上比互斥量消耗更少的资源。
    
    
    ======  总结  ======
    大道至简:  在子线程中访问获取单例对象函数,需要保证线程安全。 那么,真实开发时,如非迫不得已, 在主线程中,创建子线程之前, 先调用一次获取单例对象的函数, 所有的麻烦事都会烟消云散。

    四、单例对象的资源释放

       编写单例类时,往往会忽视对单例对象资源的释放。这里采用 “静态对象只会创建一次,在程序退出时自动销毁 ” 以及 “ 内部类 ” 两个思想,分享一种精致的设计方案,。

    ///<  Single.h------------------------------------
    
    class Single
    {
       private: 
                     Single() {}      ///<私有化构造函数,杜绝内外创建类对象。
                     static  Single *  m_instance;     ///<静态成员
    
       public:
                     static Single * instance();
                     
                     class AutoClearSingle   ///<内部类, 用来释放单例对象  【核心点1】
                     {
                           public: 
                                        ~AutoClearSingle() {
                                            if ( Single::m_instance) {
                                                       delete  Single::m_instance;
                                                       Single::m_instance  =  nullptr;
                                            }
                                        }
                     };   ///< class AutoClearSingle
    };    ///< class Single
    
    
    
    ///<  Single.cpp----------------------------------
    
    Single * Single::m_instance  =  nullptr; 
    
    Single * Single::instance()
    {
            if (m_instance == nullptr) {
                    m_instance  =  new  Single();
                    static AutoClearSingle  autoClearSingle;    ///<  【核心点2】
            }
             
            return m_instance;
    }

    。。。。看看曾经写的一个单例随笔, 现在回头看,想法真是奇怪的很。。哈哈哈。。。 单例模式 代码例子

  • 相关阅读:
    layui穿梭框内容溢出解决办法
    location之alias浅析
    Semaphore和SemaphoreSlim实现并发同步
    Barrier实现并发同步
    CountdownEvent实现并发同步
    AutoResetEvent实现并发同步
    TrieTree树
    EncryptByPassPhrase与DecryptByPassPhrase的浅说
    一个mp4转gif的网站
    vue轮播图(可随父元素高宽自适应)
  • 原文地址:https://www.cnblogs.com/azbane/p/13525688.html
Copyright © 2011-2022 走看看