1、单例模式
何为单例模式,在GOF的《设计模式:可复用面向对象软件的基础》中是这样说的:保证一个类只有一个实例,并提供一个访问它的全局访问点。首先,需要保证一个类只有一个实例;在类中,要构造一个实例,就必须调用类的构造函数,如此,为了防止在外部调用类的构造函数而构造实例,需要将构造函数的访问权限标记为protected或private;最后,需要提供要给全局访问点,就需要在类中定义一个static函数,返回在类内部唯一构造的实例。意思很明白,使用UML类图表示如下。
2、单例模式的应用场景
有一些对象其实只需要一个,比如:线程池,缓存,对话框,处理偏好设置和注册表的对象,日志对象,充当打印机,显卡等设备的驱动程序对象。这些对象只能够拥有一个实例,如果创建出了多个实例,就会导致一些程序的问题。程序的行为异常,资源使用的过量,或者导致不一致的结果。常用来管理共享的资源,比如数据库的连接或者线程池。
3、单例模式的实现
单例模式,单从UML类图上来说,就一个类,没有错综复杂的关系。但是,在实际项目中,使用代码实现时,还是需要考虑很多方面的。
1)不好的写法一:只适合在单线程环境
由于要求只能生成一个实例,因此我们必须把构造函数设为私有函数以禁止他人创建实例。我们可以定义一个静态的实例,在需要的时候创建该实例。下面定义定义的Singleton就是基于这个思路的实现:
#include<iostream>
#include<string.h>
using namespace std;
class Singleton
{
private:
Singleton(){}
static Singleton* _instance;
public:
~Singleton();
static Singleton* getInstance();
};
Singleton* Singleton::_instance = NULL;
Singleton* Singleton::getInstance()
{
if(_instance==NULL) _instance = new Singleton();
return _instance;
}
int main(int argc,char* argv[])
{
Singleton* g_singleton = Singleton::getInstance();
return 0;
}
这是最简单,也是最普遍的实现方式,代码在单线程的时候能够正常工作,但在多线程的情况下就有问题了。设想如果两个线程同时运行到判断_instance是否位NULL的if语句,并且_instance的确还没有创建时,那么两个线程都会创建一个实例,此时类型Singleton就不再满足单例模式的要求了。为了保证在多线程环境下我们还是只能得到类型的一个实例,需要加上一个同步锁。
2)不好的写法二:虽然在多线程环境中能工作但效率不高
#include<iostream>
#include<string.h>
#include <pthread.h>
using namespace std;
class Singleton
{
private:
Singleton();
static Singleton* _instance;
static pthread_mutex_t mutex;
public:
~Singleton();
static Singleton* getInstance();
};
pthread_mutex_t Singleton::mutex;
Singleton* Singleton::_instance = NULL;
Singleton::Singleton()
{
pthread_mutex_init(&mutex,NULL);
}
Singleton* Singleton::getInstance()
{
pthread_mutex_lock(&mutex);
if(_instance==NULL) _instance = new Singleton();
pthread_mutex_unlock(&mutex);
return _instance;
}
int main(int argc,char* argv[])
{
Singleton* g_singleton = Singleton::getInstance();
return 0;
}
我们还是假设有两个线程同时想创建一个实例。由于在一个时刻只有一个线程能得到同步锁,当第一线程加上锁时,第二个线程只能等待。当第一个线程发现实例还没有创建时,他创建出一个实例。接着第一个线程释放同步锁,此时第二个线程可以加上同步锁,并运行接下来的代码。这个时候由于实例已经被第一个线程创建出来了,第二个线程就不会重复创建实例了,这样就保证了我们在多线程环境中也只能得到一个实例。但是这个类还不是很完美。我们每次通过属性_instance得到Singleton的实例,都会试图加上一个同步锁,而枷锁是一个非常耗时的操作,在没有必要的时候我们应该尽量避免。
3)可行的解法:加同步锁前后两次判断实例是否已存在
我们只有在实例还没有创建之前需要加锁操作,以保证只有一个线程创建出实例。而当实例已经创建之后,我们已经不需要再做加锁操作了。于是我们可以把上述代码再做进一步的改进:
#include<iostream>
#include<string.h>
#include <pthread.h>
using namespace std;
class Singleton
{
private:
Singleton();
static Singleton* _instance;
static pthread_mutex_t mutex;
public:
~Singleton();
static Singleton* getInstance();
};
pthread_mutex_t Singleton::mutex;
Singleton* Singleton::_instance = NULL;
Singleton::Singleton()
{
pthread_mutex_init(&mutex,NULL);
}
Singleton* Singleton::getInstance()
{
if(_instance==NULL)
{
pthread_mutex_lock(&mutex);
if(_instance==NULL) _instance = new Singleton();
pthread_mutex_unlock(&mutex);
}
return _instance;
}
int main(int argc,char* argv[])
{
Singleton* g_singleton = Singleton::getInstance();
return 0;
}
上述代码只有当_instance为NULL即没有创建时,需要加锁操作。当_instance已经创建出来之后,则无须加锁。因为只有第一次的时候_instance为NULL,因此只在第一次试图创建实例的时候需要加锁。这样方法三的时间效率比方法二要好很多。方法三用加锁机制来确保在多线程环境下只创建一个实例,并且用两个if判断来提高效率。这样的代码实现起来比较复杂,容易出错。另一方面这种实现方法在平时的项目开发中用的很好,也没有什么问题?但是,如果进行大数据的操作,加锁操作将成为一个性能的瓶颈。
4)推荐方法一:利用静态变量初始化的特性
#include<iostream>
#include<string.h>
using namespace std;
class Singleton
{
private:
Singleton(){}
static Singleton* _instance;
public:
~Singleton();
static Singleton* getInstance();
};
//外部初始化,在进入主函数之前被初始化
Singleton* Singleton::_instance = new Singleton();
Singleton* Singleton::getInstance()
{
return _instance;
}
int main(int argc,char* argv[])
{
Singleton* g_singleton = Singleton::getInstance();
return 0;
}
因为静态初始化在程序开始时,也就是进入主函数之前,由主线程以单线程方式完成了初始化,所以静态初始化实例保证了线程安全性。在性能要求比较高时,就可以使用这种方式,从而避免频繁的加锁和解锁造成的资源浪费。
5)推荐方法二:利用静态局部变量的特性,C++0X以后,编译器已经保证内部静态变量的线程安全性
上边我们没有提到单例的消毁,此种实现就是为了使单例的消毁简单化:
#include<iostream>
#include<string.h>
using namespace std;
class Singleton
{
private:
Singleton(){}
public:
static Singleton* getInstance();
};
Singleton* Singleton::getInstance()
{
static Singleton _instance;
return &_instance;
}
int main(int argc,char* argv[])
{
Singleton* g_singleton = Singleton::getInstance();
return 0;
}
这里需要注意的是,C++0X以后,编译器已经保证内部静态变量的线程安全性,可以不加锁。但C++ 0X以前,仍需要加锁
#include<iostream>
#include<string.h>
using namespace std;
class Singleton
{
private:
Singleton(){}
public:
static Singleton* getInstance();
};
Singleton* Singleton::getInstance()
{
//这里加锁和解锁只是说明,C++没有现成的实现
lock();
static Singleton _instance;
unlock();
return &_instance;
}
int main(int argc,char* argv[])
{
Singleton* g_singleton = Singleton::getInstance();
return 0;
}
4、实例的消毁
在上述的五种方法中,除了第五种没有使用new操作符实例化对象以外,其余四种都使用了;我们一般的编程观念是,new操作是需要和delete操作进行匹配的;是的,这种观念是正确的。那么我们能否直接在析构函数里delete释放对象呢?直接看代码:
#include<iostream>
#include<string.h>
using namespace std;
class Singleton
{
private:
Singleton(){}
static Singleton* _instance;
public:
~Singleton();
static Singleton* getInstance();
};
Singleton* Singleton::_instance = NULL;
Singleton::~Singleton()
{
if(_instance)
{
cout << "release _instance" << endl;
delete _instance;
_instance = NULL;
}
}
Singleton* Singleton::getInstance()
{
if(_instance==NULL) _instance = new Singleton();
return _instance;
}
int main(int argc,char* argv[])
{
Singleton* g_singleton = Singleton::getInstance();
return 0;
}
程序什么输出也没有,可以看到并没有调用单例的析构函数,需要清楚以下几点:
1)单例中的 new 的对象需要delete释放。
2)delete释放对象的时候才会调用对象的析构函数。
3)如果在析构函数里调用delete,那么程序结束时,根本进不去析构函数,怎么会delete。
4)如果程序结束能自动析构,那么就会造成一个析构的循坏,所以new对应于delete。
所以单例的析构函数不会自动调用,那么可以写一个成语函数来析构实例,在使用后手动调用,代码如下:
#include<iostream>
#include<string.h>
using namespace std;
class Singleton
{
private:
Singleton(){}
static Singleton* _instance;
public:
static void destroy();
static Singleton* getInstance();
};
Singleton* Singleton::_instance = NULL;
void Singleton::destroy()
{
if(_instance)
{
cout << "release _instance" << endl;
delete _instance;
_instance = NULL;
}
}
Singleton* Singleton::getInstance()
{
if(_instance==NULL) _instance = new Singleton();
return _instance;
}
int main(int argc,char* argv[])
{
Singleton* g_singleton = Singleton::getInstance();
g_singleton->destroy();
return 0;
}
可以看到实例被消毁了。这也是最简单,最普通的处理方法了;但是,很多时候,我们是很容易忘记调用destroy函数,就像你忘记了调用delete操作一样。由于怕忘记delete操作,所以就有了智能指针;那么,在单例模型中,没有“智能单例”,该怎么办?
一个妥善的方法是让这个类自己知道在合适的时候把自己删除。或者说把删除自己的操作挂在系统中的某个合适的点上,使其在恰当的时候自动被执行。由于程序在结束的时候,系统会自动析构所有的全局变量,实际上,系统也会析构所有类的静态成员变量,就像这些静态变量是全局变量一样。我们知道,静态变量和全局变量在内存中,都是存储在静态存储区的,所以在析构时,是同等对待的。利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数 中删除单例类的实例。如下面的代码中的CGarbo类(Garbo意为垃圾工人):
#include<iostream>
#include<string.h>
using namespace std;
class Singleton
{
private:
Singleton(){}
static Singleton* _instance;
class CGarbo
{
public :
~CGarbo()
{
if (_instance != NULL )
{
cout<< "release _instance" <<endl;
delete _instance;
_instance = NULL ;
}
}
};
static CGarbo cg;
public:
static Singleton* getInstance();
};
Singleton* Singleton::_instance = NULL;
Singleton::CGarbo Singleton::cg;
Singleton* Singleton::getInstance()
{
if(_instance==NULL) _instance = new Singleton();
return _instance;
}
int main(int argc,char* argv[])
{
Singleton* g_singleton = Singleton::getInstance();
return 0;
}
类CGarbo被定义为Singleton的私有内嵌类,以防该类被在其它地方滥用。
在程序运行结束时,系统会调用Singleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。而这种资源的释放方式是在程序员“不知道”的情况下进行的,程序员不用特别的去关心,使用单例模式的代码时,不必关心资源的释放。
使用这种方法释放单例对象有以下特征:
1)在单例类内部定义专有的嵌套类。
2)在单例类内定义私有的专门用于释放的静态成员。
3)利用程序在结束时析构全局变量的特性,选择最终的释放时机。
4)使用单例的代码不需要任何操作,不必关心对象的释放