zoukankan      html  css  js  c++  java
  • 单例模式

    1.概述

      单例模式用来保证系统中一个类只有一个实例且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。

      我的理解是:单例模式的实质就是私有化构造函数(保证只有一个实例)、析构函数(保证只有单例类能删除自己,防止其他类删除单例类造成悬浮引用)、拷贝构造函数(禁止类拷贝)、重载=操作符(禁止类赋值)。

     

    2.常用的场景

      单例模式常常与工厂模式结合使用,因为工厂只需要创建产品实例就可以了,在多线程的环境下也不会造成任何的冲突,因此只需要一个工厂实例就可以了。

     

    3.全局变量和单例模式的比较

      采用全局或者静态变量,会影响封装性,难以保证别的代码不会对全局变量造成影响。

      全局变量可以提供单例模式实现的全局访问这个功能,但它不能保证应用程序只有一个实例。

     

    4.代码及分析

    1)懒汉式单例模式

      懒汉式是典型的时间换空间。就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。

     1 class Singleton {
     2 public:
     3     static Singleton* getInstance();
     4     //析构的时候释放资源~
     5     ~Singleton() {
     6         if( (_instance != NULL) 
     7             delete _instance;
     8     }
     9 protected:
    10     Singleton();
    11 private:
    12     static Singleton* _instance;
    13 }
    14  
    15 Singleton *Singleton::_instance = NULL;
    16 Singleton* Singleton::getInstance() {
    17     if( _instance == NULL) {
    18         _instance = new Singleton();
    19     }
    20     return _instance;
    21 }

      在单线程环境可以通过以上方式实现单例模式,但是在多线程的环境下,若两个线程同时运行到if (instance == NULL)这一句,导致可能会产生两个实例。于是就要在代码中加锁

     1 class Lock
     2 {
     3 private:       
     4     mutex mtex;
     5 public:
     6     Lock(mutex m) : mtex(m)
     7     {
     8         mtex.Lock();
     9     }
    10     ~Lock()
    11     {
    12         mtex.Unlock();
    13     }
    14 };
    15 
    16 class Singleton {
    17 public:
    18     static Singleton* getInstance();
    19     //析构的时候释放资源~
    20     ~Singleton() {
    21         if( (_instance != NULL) 
    22             delete _instance;
    23     }
    24 protected:
    25     Singleton();
    26 private:
    27     static Singleton* _instance;<br>    static mutex m;
    28 }
    29  
    30 Singleton *Singleton::_instance = NULL;
    31 Singleton* Singleton::getInstance() {    
    32     //check 之前进行临界区加锁操作    
    33     Lock lock(m);
    34     if( _instance == NULL) {
    35         _instance = new Singleton();
    36     }
    37     return _instance;
    38 }

      如上,如果已经有一个线程进入访问,其他线程必须等待,这样就能够保证多线程情况下实例的唯一。但互斥的同步会导致性能的降低,因为即使_instance已经不为空了,每次还是需要加锁,这样操作花费就比较多,性能必定比较差。

      解决的办法是在加锁前先判断实例是否为空,即加 “双重检验加锁”。

     1 class Lock
     2 {
     3 private:       
     4     mutex mtex;
     5 public:
     6     Lock(mutex m) : mtex(m)
     7     {
     8         mtex.Lock();
     9     }
    10     ~Lock()
    11     {
    12         mtex.Unlock();
    13     }
    14 };
    15 class Singleton {
    16 public:
    17     static Singleton* getInstance();
    18     //析构的时候释放资源~
    19     ~Singleton() {
    20         if( (_instance != NULL) 
    21             delete _instance;
    22     }
    23 protected:
    24     Singleton();
    25 private:
    26     static Singleton* _instance;
    27     static mutex m;
    28 }
    29  
    30 Singleton *Singleton::_instance = NULL;
    31 Singleton* Singleton::getInstance() {
    32     //check 之前进行临界区加锁操作
    33     //双重检验加锁
    34     if(_instance == NULL ) {
    35         Lock lock(m);
    36         if( _instance == NULL) {
    37             _instance = new Singleton();
    38         }
    39     }
    40     return _instance;
    41 }

      这样只需要在第一次调用的时候加锁,可大大提高效率。

    2)饿汉式单例模式

      饿汉式是典型的空间换时间,当类装载的时候就会创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。。

     1 class Singleton {
     2 public:
     3     static Singleton* getInstance();
     4     //析构的时候释放资源~
     5     ~Singleton() {
     6             delete _instance;
     7     }
     8 protected:
     9     Singleton();
    10 private:
    11     static Singleton* _instance;
    12 }
    13  
    14 Singleton *Singleton::_instance = new Singleton();
    15 Singleton* Singleton::getInstance() {
    16     return _instance;
    17 }

      这样的话同样会导致问题:无论这个类是否被使用,都会创建一个instance对象。如果这个创建过程很耗时,比如需要连接10000次数据库,并且这个类还并不一定会被使用,那么这个创建过程就是无用的。

      解决的办法是添加静态内部类。

      以下,参考http://blog.csdn.net/hackbuteer1/article/details/7460019

    3)懒汉式单例模式--静态内部类方式

     1 class CSingleton
     2 {
     3 private:
     4     CSingleton(){}
     5     static CSingleton *m_pInstance;
     6     class CGarbo   //它的唯一工作就是在析构函数中删除CSingleton的实例
     7     {
     8     public:
     9         ~CGarbo()
    10         {
    11             if(CSingleton::m_pInstance)
    12             delete CSingleton::m_pInstance;
    13         }
    14     };
    15     static CGarbo Garbo;  
    16     //定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数
    17 public:
    18     static CSingleton * GetInstance()
    19     {
    20         if(m_pInstance == NULL)  //判断是否第一次调用
    21         m_pInstance = new CSingleton();
    22         return m_pInstance;
    23     }
    24 };
    25     

      

      类CGarbo被定义为CSingleton的私有内嵌类,以防该类被在其他地方滥用。

      程序运行结束时,系统会调用CSingleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。

    4)懒汉式单例模式--静态局部变量方式

      但是添加一个类的静态对象,总是让人不太满意,所以有人用如下方法来重新实现单例和解决它相应的问题,代码如下:

     1 class CSingleton
     2 {
     3 private:
     4     CSingleton()   //构造函数是私有的
     5     {
     6     }
     7 public:
     8     static CSingleton & GetInstance()
     9     {
    10         static CSingleton instance;   //局部静态变量
    11         return instance;
    12     }
    13 };

      使用局部静态变量,完全实现了单例的特性,而且代码量更少,也不用担心单例销毁的问题。
      但使用此种方法也会出现问题,当Singleton singleton = Singleton :: GetInstance();时,就出现了一个类拷贝的问题,违背了单例的特性。产生这个问题原因在于:编译器会为类生成一个默认的构造函数,来支持类的拷贝。

      要禁止类拷贝和类赋值,禁止程序员用这种方式来使用单例,可以将拷贝构造函数和重载=操作符声明为私有函数。见5)扩展后的单例模式。

    5)扩展后的单例模式

      考虑到线程安全、异常安全,可以做以下扩展

     1 class Lock
     2 {
     3 private:       
     4     CCriticalSection m_cs;
     5 public:
     6     Lock(CCriticalSection  cs) : m_cs(cs)
     7     {
     8         m_cs.Lock();
     9     }
    10     ~Lock()
    11     {
    12         m_cs.Unlock();
    13     }
    14 };
    15 
    16 class Singleton
    17 {
    18 private:
    19     Singleton();
    20     Singleton(const Singleton &);                // 私有拷贝函数
    21     Singleton& operator = (const Singleton &);   // 私有重载=操作符函数
    22 
    23 public:
    24     static Singleton *Instantialize();
    25     static Singleton *pInstance;
    26     static CCriticalSection cs;
    27 };
    28 
    29 Singleton* Singleton::pInstance = 0;
    30 
    31 Singleton* Singleton::Instantialize()
    32 {
    33     if(pInstance == NULL)
    34     {   //double check
    35         Lock lock(cs);          
    36         //用lock实现线程安全,用资源管理类,实现异常安全
    37         //使用资源管理类,在抛出异常的时候,资源管理类对象会被析构,析构总是发生的无论是因为异常抛出还是语句块结束。
    38         if(pInstance == NULL)
    39         {
    40             pInstance = new Singleton();
    41         }
    42     }
    43     return pInstance;
    44 }

     

  • 相关阅读:
    2018年国内就业薪资高的7大编程语言排行
    前端css实现最基本的时间轴
    前端css实现最基本的时间轴
    用Canvas画一个刮刮乐
    用Canvas画一个刮刮乐
    「干货」从菜鸟到大神,前端学习书籍推荐
    洛谷P3379 【模板】最近公共祖先(LCA)
    洛谷 P1359 租用游艇
    位运算...
    洛谷P2782 友好城市
  • 原文地址:https://www.cnblogs.com/SnailProgramer/p/4253348.html
Copyright © 2011-2022 走看看