单例模式是为了解决唯一对象实例问题而提出来的,许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。或者像是一个系统的管理员,由唯一一个管理员来管理各种数据的存取等。
实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
在处理单例模式的时候,我们需要注意的是在多线程状态的时候,因为如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。
在一般情况下,我们是不需要用到多线程的,在单线程下的单例模式比较简单。代码如下:
1 ///Singleton.h 2 3 #ifndef SINGLETON_H 4 #define SINGLETON_H 5 6 class Singleton { 7 public: 8 ~Singleton(); 9 static Singleton* getInstance(); 10 protected: 11 Singleton(); 12 private: 13 static Singleton* instance; 14 }; 15 #endif
1 //Singleton.cpp 2 3 #include "Singleton.h" 4 #include <iostream> 5 6 using namespace std; 7 8 Singleton::Singleton() { 9 cout << "Singleton..." << endl; 10 } 11 Singleton::~Singleton() { 12 cout << "~Singleton..." << endl; 13 } 14 15 Singleton* Singleton::instance = NULL; 16 17 Singleton* Singleton::getInstance() { 18 if (instance == NULL) 19 instance = new Singleton(); 20 21 return instance; 22 }
下面我们来看看多线程,在高并发的环境下,上面的代码是不安全的,在高并发环境中,getInstance()方法可能返回多个指向不同的该类实例,如下图所示:
如果这两个线程按照上述的步骤来执行,不难看出,在时刻1和时刻2,由于还没有创建单例对象,Thread1和Theread2都会进入创建单例莫斯的代码块分别创建实例。在时刻3,Thread1创建了一个单例对象,但此时Thread2已经无法知道,它仍然会继续创建一个新的实例对象。于是这两个线程持有的实例并非为同一个。更为糟糕的是,在没有自动回收机制的语言平台如C++上运行这样的单例模式,因为我们认为创建了一个单例实例,忽略了其他线程所产生的对象,不会手动地去回收它们,会引起内存泄漏。因此我们要使用锁来保证只产生一个实例。
使用double-check来保证thread safety.但是如果处理大量数据时,该锁会成为严重的性能瓶颈。这种方法被称为懒汉模式。
1 class Singleton 2 { 3 public: 4 static Singleton* getInstance(); 5 protected: 6 Singleton(); 7 private: 8 static Singleton* instance; 9 }; 10 11 Singleton* Singleton::m_instance = NULL; 12 13 Singleton::Singleton() { } 14 15 Singleton* Singleton::getInstance() 16 { 17 if(NULL == m_instance) 18 { 19 Lock();//借用其它类来实现,如boost 20 if(NULL == m_instance) 21 { 22 m_instance = new Singleton; 23 } 24 UnLock(); 25 } 26 return m_instance; 27 }
还有一种方法,被称为饿汉模式,即无论是否调用该类的实例,在程序开始时就会产生一个该类的实例,并在以后仅返回此实例。
由静态初始化实例保证其线程安全性,因为静态实例初始化在程序开始时进入主函数之前就由主线程以单线程方式完成了初始化,不必担心多线程问题。故在性能需求较高时,应使用这种模式可以避免频繁的锁争夺。
1 class Singleton 2 { 3 public: 4 static Singleton* getInstance() { 5 return m_instance; 6 } 7 8 private: 9 static const Singleton* m_instance; 10 Singleton(){} 11 }; 12 13 const Singleton* Singleton::m_instance = new Singleton;