Singleton非常的有用,但通常也是烦恼之源。本文包括了个人实践中遇到的各种各样的Singleton,同时包括了C++和Java部分。
关于什么是Singleton的思考。
Singleton模式通常用于只需要一个对象生存的场合,但是这句话不是Singleton的全部意义,模式不是公式,它是可以变化的。比如:一个系统打印对象,进程内只需要一个,但是一个多线程并发访问数据库的程序,每个线程可能都需要一个连接对象,这样Singleton就意味着进程内有多个,每个线程里面有一个。但是如果允许连接多个数据库呢?很有可能就变成了每个线程内只允许有一个连着某个特定数据库的连接对象。
Singleton的实现者需要提供一个全局的访问点给用户。最简单的就是静态成员函数Instance,为什么不用全局变量,因为全局变量不能阻止别人创建同类型的变量,而且也污染了全局空间(别人不能和你用一样的变量明)。所以我们要把类的构造函数变为protected。一个对象内部可以保存一个静态指针,然后在Instance函数内部实例化,并返回它。这种解决方案可以解决刚才打印机的要求。一个对象可以保存一个静态map,map每一项保存线程ID和静态对象指针,并且提供维护该map的一系列方法,这样就可以解决每个线程需要有一个对象的要求。但是这样就够了么,我还碰到一个不同的需求,要求运行时决定创建不同的对象。这时候可以在Instance函数上加上参数,通过参数来创建不同的对象。这些对象也可以派生自父Singleton类。也许每个线程允许对象数目不能超过3个,没关系,我们可以在Instance内部实现这些控制逻辑。
我想表达的是,Singleton可以有很多变种,有时候甚至让你感觉名不副实,但是这就是设计模式的魅力。我也是从一开始的教条主义到能接受很多精彩的变化,有时候甚至都不应该用它,或许很多是多态就够了
C++部分
http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf 这是一篇Scott Meyers and Andrei Alexandrescu的论文。
1)不要用静态或者全局变量来实现Singleton
由于C++不能保证静态或者全局对象的构造函数的调用顺序以及析构顺序。所以如果程序中有多个用此方法实现的Singleton类,它们之间又有某种构造依赖关系和析构依赖关系,就会造成灾难性的后果。所以,只有当肯定不会有构造和析构依赖关系的情况下,这种实现才是合适的。不过,对于C++,采用另一种静态对象的方式更加简单和方便。因此这种方式基本上可以放弃了。
2)Meyers Singleton来控制构造顺序,但是不能控制析构顺序
Scott Meyer在<
3)Andrei在<
a.用new来分配Singleton对象,
b.每个Singleton对象都有一个整形寿命计数器,值大者寿命长
c.用一个特殊设计的数组来保存需要被销毁的Singleton对象的指针。寿命越长的总是在数组前面,寿命相同的按照创建顺序由前到后排列。
d.在std::at_exit中注册一个清理函数,该函数总是从c描述的数组中取出最后一个指针,然后调用delete。
4)支持多线程。能够控制构造和析构的顺序之后,现在考虑多线程。一般采用Double-Checked Locking模式。第一次check不用加锁,但是第二次check和创建对象必须加锁。还要注意编译器可能会优化代码,导致Double-Checked Locking模式失效。因此要使用volatile 修饰T* pInstance变量。
5)Loki最后提出了一个基于策略的SingletonHolder类,完美的解决了以上问题。注意SingletonHolder只支持一般意义的单例,也就是进程内唯一对象。SingletonHodler提供了更多的策略类来满足不同的要求。具体可以参考Loki文档或者。SingletonHolder接收三个策略类,分别用于管理创建和销毁对象,生命周期和线程策略。具体使用例子可以参考书本。
6)ACE和boost都提供了各自的解决方案。相比而言,SingletonHolder更灵活,能够面对各种情况。
SingletonHolder例子:
下载最新版源代码,在自己的C++程序中设置好include目录,并将singleton.cpp加入到makefile中。
#include
#include "loki/Singleton.h"
#include
using namespace std;
class MyClass{
public:
void ShowPtr()
{
cout << this << endl;
}
};
unsigned int GetLongevity(MyClass*){
return 1;
}
/*
*
*/
int main(int argc, char** argv) {
MyClass c=Loki::SingletonHolder
c.ShowPtr();
return 0;
}
如果要支持多线程,应该在自己的应用程序中定义这个宏:LOKI_CLASS_LEVEL_THREADING。使用下面的代码创建:
MyClass c=Loki::SingletonHolder<
MyClass,
Loki::CreateUsingNew,
Loki::SingletonWithLongevity,
Loki::ClassLevelLockable,
Loki::Mutex
>::Instance();
既然是多线程环境下的Singleton,那么就应该使用Mutext进行同步,并且使用volatile强制从内存中读取数据。
SingletonHolder只支持标准Singleton,即进程内唯一。不支持变种的Singleton,比如每个线程只有一个对象,或者其他的情况,需要我们自己设计。
Java部分
1.public static final 成员变量,同时将构造函数变为private.客户程序直接访问成员变量即可。
2.private static final 成员变量。然后提供工程方法getInstance(),返回静态成员变量.由于工厂方法带来的灵活性,我们可以讲进程内唯一对象,变为线程内唯一对象。在修改getInstance方法的实现代码时不会影响到客户代码的调用。
3.Single类最好不要实现Serializable接口。因为每次