Singleton_单例模式
单例模式是什么?有什么用?用在哪里?
(以下内容摘自wikipedia)
单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。如,IO处理,数据库操作等,这些对象都要占用重要的系统资源。又比如,在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。
单例模式的构建方式
首先,private构造函数,这确保外部无法实例这个类,即无法生成这个类的对象。从而防止多个对象出现。
但是,我们的目的是有且仅有一个该类的对象,那这一个如何生成?
因为构造函数设为私有成员,则只能在类的内部被访问。那么我们就编写一个类内成员函数来访问这个私有构造函数。
但是,成员函数也是需要该类的一个对象调用的啊,这岂不是个鸡生蛋,蛋生鸡的问题了!
如何解决呢,没错,就是将该函数声明为static。
因为static成员归整个类所共有。static函数不与对象进行绑定,这也意味着static函数不存在this指针。
static方式形成实例:getInstance() (通常都叫这个函数名)
这个方法实际上是在new自己,因为其可以访问私有的构造函数,所以他是可以保证对象被创建出来。
#include <iostream> #include <string> #include <vector> using namespace std; /* * 1.把构造函数设为私有,外面无法直接生成对象 * 2. 在类内部添加函数,生成对象,但是不可行 * 3. 把该函数设为static * 4. 此时可以生成对象,但是不唯一 */ class Singleton { public: static Singleton *getInstance() { Singleton *pInstance_ = new Singleton; return pInstance_; } private: Singleton(){} }; int main(int argc, const char *argv[]) { //Singleton s; Singleton *ps = Singleton::getInstance(); cout << ps << endl; Singleton *ps2 = Singleton::getInstance(); cout << ps2 << endl; return 0; }
但是,这样生成的对象仍然不唯一。因为我们还是可以创建多个singleton的指针从而new出多个实例,如程序中的ps, ps2.
如何解决呢,我们把getInstance()内部的局部指针pInstance_设为该类的static指针,然后每次去检查该static指针是否为NULL(第一次访问),只有是第一次访问时,才去生成这个对象。
#include <iostream> #include <string> #include <vector> using namespace std; /* * 把getInstance内部的局部指针设为该类的static指针 * 然后每次去检查该static指针是否为NULL */ class Singleton { public: static Singleton *getInstance() { //pInstance_是一种共享资源 //因此这里存在竞态问题 if(pInstance_ == NULL){ pInstance_ = new Singleton; } return pInstance_; } private: Singleton(){} static Singleton *pInstance_; }; Singleton *Singleton::pInstance_ = NULL; int main(int argc, const char *argv[]) { //Singleton s; Singleton *ps = Singleton::getInstance(); cout << ps << endl; Singleton *ps2 = Singleton::getInstance(); cout << ps2 << endl; return 0;
}
以上这段代码虽然保证了在该单线程环境中创建了唯一的一个实例。
但是,在多线程环境下仍然会存在竞态问题。比如两个线程同时检测到pInstance_指针为空,那么还是会那样还是会导致创建多个Singleton类的实例。
多线程时的程序如下:
#include <iostream> #include <string> #include <vector> using namespace std; class Singleton { public: static Singleton *getInstance() { //pInstance_是一种共享资源 //因此这里存在竞态问题 if(pInstance_ == NULL){ sleep(1); pInstance_ = new Singleton; } return pInstance_; } private: Singleton(){} static Singleton *pInstance_; }; Singleton *Singleton::pInstance_ = NULL; void *threadFunc(void *arg) { Singleton *ps = Singleton::getInstance(); cout << ps << endl; return NULL; } int main(int argc, const char *argv[]) { vector<pthread_t> vec(10); for(vector<pthread_t>::iterator it = vec.begin(); it != vec.end(); ++it){ //it是个对象,只是具有指针的特性 pthread_create(&*it, NULL, threadFunc, NULL); } for(vector<pthread_t>::iterator it = vec.begin(); it != vec.end(); ++it){ pthread_join(*it, NULL); } return 0; }
那么如何解决呢,这里我们可以在每次检查指针之前都进行加锁操作。由于lock是确保当一个线程位于代码的临界区时,另一个线程不能进入临界区(同步操作)。如果其他线程试图进入锁定的代码,则它将一直等待,直到该对象被释放。从而确保在多线程下不会创建多个对象实例了。
(以下内容摘自wikipedia)
互斥锁(Mutual exclusion,缩写 Mutex)是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区域(critical section)达成。临界区域指的是一块对公共资源进行访问的代码,并非一种机制或是算法。一个程序、进程、线程可以拥有多个临界区域,但是并不一定会应用互斥锁。
需要此机制的资源的例子有:旗标、队列、计数器、中断处理程序等用于在多条并行运行的代码间传递数据、同步状态等的资源。维护这些资源的同步、一致和完整是很困难的,因为一条线程可能在任何一个时刻被暂停(休眠)或者恢复(唤醒)。
例如:一段代码(甲)正在分步修改一块数据。这时,另一条线程(乙)由于一些原因被唤醒。如果乙此时去读取甲正在修改的数据,而甲碰巧还没有完成整个修改过程,这个时候这块数据的状态就处在极大的不确定状态中,读取到的数据当然也是有问题的。更严重的情况是乙也往这块地方写数据,这样的一来,后果将变得不可收拾。因此,多个线程间共享的数据必须被保护。达到这个目的的方法,就是确保同一时间只有一个临界区域处于运行状态,而其他的临界区域,无论是读是写,都必须被挂起并且不能获得运行机会。
好了, 接下来继续看加锁后的程序。
#include <iostream> #include <string> #include <vector> #include "MutexLock.h" using namespace std; class Singleton { public: static Singleton *getInstance() { mutex_.lock(); if(pInstance_ == NULL){ sleep(1); pInstance_ = new Singleton; } mutex_.unlock(); return pInstance_; } private: Singleton(){} static Singleton *pInstance_; static MutexLock mutex_; //互斥锁 }; Singleton *Singleton::pInstance_ = NULL; MutexLock Singleton::mutex_; void *threadFunc(void *arg) { Singleton *ps = Singleton::getInstance(); cout << ps << endl; return NULL; } int main(int argc, const char *argv[]) { vector<pthread_t> vec(10); for(vector<pthread_t>::iterator it = vec.begin(); it != vec.end(); ++it){ //it是个对象,只是具有指针的特性 pthread_create(&*it, NULL, threadFunc, NULL); } for(vector<pthread_t>::iterator it = vec.begin(); it != vec.end(); ++it){ pthread_join(*it, NULL); } return 0; }
但是,这样每次获取对象并进行检查时都要进行加锁,锁的争用过多,这是极其影响效率的。
那么如何解决呢,我们引入“双重锁”模式(Double Check Lock Pattern, DCLP)
我们只需在同步操作之前,先判断该实例是否为NULL,这是外部锁;如果为NULL,也就是该实例还没有被创建,那么再通过加锁的方式来创建该实例,也就是内部锁。这种双重判断的模式,内部锁,保证结果的正确性;外面锁,保证大部分线程不会进入争用锁。
代码如下:
#include <iostream> #include <string> #include <vector> #include "MutexLock.h" using namespace std; class Singleton { public: static Singleton *getInstance() { if(pInstance_ == NULL){ //外部判断:如果这个实例还没有被创建 mutex_.lock(); //内部加锁:保证只有一个线程用来创建实例 if(pInstance_ == NULL){ //保证在该线程种只有一个实例被创建 sleep(1); pInstance_ = new Singleton; } mutex_.unlock(); } return pInstance_; } private: Singleton(){} static Singleton *pInstance_; static MutexLock mutex_; }; Singleton *Singleton::pInstance_ = NULL; MutexLock Singleton::mutex_; void *threadFunc(void *arg) { Singleton *ps = Singleton::getInstance(); cout << ps << endl; return NULL; } int main(int argc, const char *argv[]) { vector<pthread_t> vec(10); for(vector<pthread_t>::iterator it = vec.begin(); it != vec.end(); ++it){ pthread_create(&*it, NULL, threadFunc, NULL); } for(vector<pthread_t>::iterator it = vec.begin(); it != vec.end(); ++it){ pthread_join(*it, NULL); } return 0; }
这下终于算是搞好了这个单例模式......