一.概述:

  因为在设计或开发中,肯定会有这么一种情况,一个类只能有一个对象被创建,如果有多个对象的话,可能会导致状态的混乱和不一致。这种情况下,单例模式是最 恰当的解决办法。有很多地方需要这样的功能模块,如系统的日志输出,GUI应用必须是单鼠标,MODEM的联接需要一条且只需要一条电话线,操作系统只能有一个窗口管理器,一台PC连一个键盘。单例模式有很多种实现方式,各自的特性不相同,使用的情形也不相同。今天要实现的是常用的三种,分别是饿汉式、懒汉式和多线程式。

  《设计模式》一书中的实现有三个要素,定义一个单例类,要使用类的私有静态指针变量指向类的唯一实例(即在类中就生成一个对象),并用一个公有的静态方法获取该实例,并把构造函数定义为protected或private。




二.懒汉式实现单例模式:

懒汉式的特点是延迟加载,懒汉么,很懒,它只在要用到实例时才加载实例。

 /****************************************                                   
  2     > File Name:lanhan.cpp
  3     > Author:xiaoxiaohui
  4     > mail:1924224891@qq.com
  5     > Created Time:2016年05月07日 星期六 15时01分25秒
  6 ****************************************/
  7 
  8 #include<iostream>
  9 using namespace std
 10 
 11 class Singleton
 12 {
 13 private:
 14     Singleton()
 15     {}
 16     static Singleton* _instace;   //静态的  私有的
 17 public:
 18     static Singleton* GetInstace()
 19     {
 20         if(_instance == NULL)
 21         {
 22             _instance = new Singleton();
 23         }
 24         return _instance;   //如果非空则new一个对象 后者返回原来的两个对象(所以保证了只有一个对象生成)   
 25     }
 26  
 27 }
 28

上面的这一实现存在内存泄露问题,因为没有释放_instance指针,下面为懒汉式的改进版:

 8 #include<iostream>
  9 using namespace std
 10 
 11 class Singleton
 12 {
 13 private:
 14     Singleton()
 15     {}
 16     static Singleton* _instance;   //静态的  私有的
 17 
 18     class del
 19     {
 20     public:
 21         ~del()
 22         {
 23             if(Singleton::_instance != NULL)
 24             {
 25                 delete Singleton::_instance;
 26                 Singleton::_instance = NULL;
 27             }
 28         }
 29     }
 30     static del d;  //静态变量会在程序结束时调用它的析构函数
 31 public:
 32     static Singleton* GetInstance()
 33     {
 34         if(_instance == NULL)
 35         {
 36             _instance = new Singleton();
 37         }
 38         return _instance;   //如果非空则new一个对象 后者返回原来的两个对象(所以保证了只有一个对象生成)   
 39     }
 40 
 41 }

该实现会在程序结束时调用静态变量的析构函数,从而delete了唯一的Singleton对象。

使用这种方法释放单例对象有以下特征:
1.在单例类内部定义专有的嵌套类。
2.在单例类内定义私有的专门用于释放的静态成员。
3.利用程序在结束时析构全局变量的特性,选择最终的释放时机。


但是现在还有问题,如果在多线程环境下,因为“if(_instance == NULL)”并不是原子的,会存在线程安全问题(如果一个线程刚刚判断了指针为空,这时另一个线程的优先级更高或者其它原因,打断了原来线程的执行,再次判断指针也会为空,所以会出现两个实例)下面为多线程环境下的懒汉式单例模式:

 8 #include<iostream>
  9 #include<stdlib.h>
 10 #include<pthread.h>
 11 
 12 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
 13 using namespace std
 14 
 15 class Singleton
 16 {
 17 private:
 18     Singleton()
 19     {}
 20     static Singleton* _instance;   //静态的  私有的
 21 
 22     class del
 23     {
 24     public:
 25         ~del()
 26         {
 27             if(Singleton::_instance != NULL)
 28             {
 29                 delete Singleton::_instance;
 30                 Singleton::_instance = NULL;
 31             }
 32         }
 33     }
 34     static del d;  //静态变量会在程序结束时调用它的析构函数
 35 public:
 36     static Singleton* GetInstance()
 37     {
 38         pthread_mutex_lock(&lock);
 39         if(_instance == NULL)
 40         {
 41             _instance = new Singleton();
 42         }
 43         pthread_mutex_unlock(&lock);
 44         return _instance;   //如果非空则new一个对象 后者返回原来的两个对象(所以保证了只有一个对象生成)   
 45     }
 46 
 47 }
 48

但现在还有问题,当有大量的线程时,只会有一个线程进入互斥锁,然后执行下面的代码而其它线程只能等待,并且加锁是一个繁重的过程,这样会导致加很多次锁,这样就太不高效了。下面是高效版的多线程环境下的懒汉式单例模式:

 8 #include<iostream>
  9 #include<stdlib.h>
 10 #include<pthread.h>
 11 
 12 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
 13 using namespace std
 14 
 15 class Singleton
 16 {
 17 private:
 18     Singleton()
 19     {}
 20     static Singleton* _instance;   //静态的  私有的
 21 
 22     class del
 23     {
 24     public:
 25         ~del()
 26         {
 27             if(Singleton::_instance != NULL)
 28             {
 29                 delete Singleton::_instance;
 30                 Singleton::_instance = NULL;
 31             }
 32         }
 33     }
 34     static del d;  //静态变量会在程序结束时调用它的析构函数
 35 public:
 36     static Singleton* GetInstance()
 37     {
 38         if(_instance == NULL)
 39         {
 40              pthread_mutex_lock(&lock);
 41              if(_instance == NULL)
 42              {
 43                 _instance = new Singleton();
 44              }
 45              pthread_mutex_unlock(&lock);
 46         }
 47         return _instance;   //如果非空则new一个对象 后者返回原来的两个对象(所以保证了只有一个对象生成)   
 48     }
 49 
 50 }

这样只有没有Singleton实例时才会进入加锁的代码,而当有Singleton实例时不需要进入加锁的代码中,直接返回已存在的实例就行了。




三.饿汉式的单例模式:在一开始就创建实例,要用时直接返回即可。饿汗式的单例模式没有线程安全问题,因为所以线程都只能访问一个已存在的对象,无论线程怎么调度都不会有多个对象出现。因为对象是一个静态变量(不是指针),会在程序结束时自动调用它的析构函数,所以不用考虑内存泄露问题。

饿汗式的特点:代码简单,不会出现内存泄露,是线程安全的。

  1 /****************************************
  2     > File Name:erhan.cpp
  3     > Author:xiaoxiaohui
  4     > mail:1924224891@qq.com
  5     > Created Time:2016年05月07日 星期六 16时10分56秒
  6 ****************************************/
  7 
  8 #include<iostream>
  9 using namespace std
 10 
 11 
 12 class Singleton
 13 {
 14 private:
 15     Singleton()
 16     {}
 17     static Singleton instance ;    //静态变量只会有一份数据存在 从而保证只有一个实例
 18 public:
 19     static Singleton& GetInstance()
 20     {
 21         return instance;                                                   
 22     }   
 23 }

声明一个局部的静态变量,而静态变量在全局范围内只有一份数据,所以无论调用多少此GetInstance,返回的都是那一个实例。

但这个实现存在问题,Singleton singleton = Singleton :: GetInstance(),这么做就出现了一个类拷贝的问题,这就违背了单例的特性。产生这个问题原因在于:因为在这里没有实现拷贝构造函数,编译器会为类生成一个默认的拷贝构造函数,来支持类的拷贝。

解决方法:1.自己再定义一个拷贝构造函数和operator=,这个拷贝构造函数和operator=什么都不做。

2.返回一个Singleton指针。

下面为方法2的代码:

  8 #include<iostream>
  9 using namespace std
 10 
 11 
 12 class Singleton
 13 {
 14 private:
 15     Singleton()
 16     {}
 17     static Singleton instance ;    //静态变量只会有一份数据存在 从而保证只有一个实例
 18 public:
 19     static Singleton* GetInstance()
 20     {
 21         return &instance;                                                   
 22     }   
 23 }



总结:单例模式适用于只允许一个实例存在的情况,它的实现必须满足三个条件,一是必须在类中就定义一个实例;二是必须有一个公有的静态方法来获取该实例;三是构造函数必须是私有的,来保证不容许别人通过调用构造函数来生成一个实例。在实现时要注意内存泄露问题,线程安全问题,性能问题。