单例模式是最简单的设计模式,就让我像玩简单的游戏同样写下去吧。程序员
v1: 简单模式设计模式
和这个版本有过一面之缘,但不敢苟同。安全
class Singleton { private: Singleton() {} public: static Singleton * getIns() { static Singleton * ins = new Singleton(); return ins; } };
问题:什么时候析构不明确;最重要的是调用屡次getIns函数会产生多个static Singleton指针,指向每次都调用都new出来的实例。多线程
v2: 通常模式函数
典型写法学习
class Singleton { private: Singleton() {} static Singleton * ins; public: static Singleton * getIns() { if(!ins) ins = new Singleton(); return ins; } }; static Singleton * Singleton::ins = NULL;
问题:仍然未考虑析构问题;对象可能被复制出多个副本。测试
Java中因为容许在调用构造函数以前先初始化变量,所以有这样一种写法:spa
public class Singleton { private Singleton() {} public static Singleton ins = new Singleton(); public static Singleton * getIns() { return ins; } };
简洁明了,也是蛮OK啦,析构也省了,而且因为初始化这个语句是JVM作的,所以人工的同步也省了(不带这么欺负C++程序员的 = =)。线程
v3: 增强模式设计
加入私有的复制构造函数以防出现单例对象的副本;加入一个内部静态类,整个程序结束后,静态类随着其余静态变量消亡,此时调用析构函数将ins析构。
class Singleton { private: Singleton() {} Singleton(const Singleton & s) {} Singleton & operator = (const Singleton & s) {} static Singleton * ins; public: static Singleton * getIns() { if(!ins) ins = new Singleton(); return ins; } class CGarbo // 内部类 { public: ~CGarbo() { if(Singleton::ins) delete Singleton::ins; } }; static CGarbo Garbo; }; static Singleton * Singleton::ins = NULL;
问题:不是线程安全的。
v4: hard模式
static Singleton * getIns() { pthread_mutex_lock( &mutex_lock ); if(!ins) ins = new Singleton(); pthread_mutex_unlock( &mutex_lock ); return ins; }
问题:无论ins是否是空,都要加锁,代价高
v5: 再接再励模式
当ins不为空时,反正getIns函数无论读写,直接返回就行了,读写锁尽可交给使用者。
static Singleton * getIns() { if(!ins) { pthread_mutex_lock( &mutex_lock ); if(!ins) ins = new Singleton(); pthread_mutex_unlock( &mutex_lock ); } return ins; }
天然,getIns加了锁,析构的时候也一样要加锁。继续学习,再接再励!
v6:虎牢关模式
实际上,只有在第一次建立单例的时候须要得到锁。既然能够用一个内部类的静态实例实现单例对象的自动释放,何不把该静态对象的初始化也交由该内部类来维护呢:
class Singleton { public: Singleton() {} Singleton(const Singleton &s) {} Singleton & operator = (const Singleton & s) {} public: static Singleton *getInstance() { return GC::pinstance; } class GC { public: static Singleton *pinstance; GC() { pinstance = new Singleton(); } ~GC() { if( pinstance ) { delete pinstance; } } }; GC gc; }; Singleton * Singleton::GC::pinstance = nullptr;
注意,此处将Singleton的构造函数改为了public。由于在系统中没有Singleton实例时,Singleton的static成员也是不会被建立的,所以需将构造函数改为public,在使用词单例类时,先声明一个Singleton对象。
如此,进程启动,在初始化阶段先初始化了不属于任何实例对象的Singleton的各个静态成员,顺便new好了咱们所需的单例对象指针,这一过程不是线程来干的,所以这样的new操做是安全的。而在程序运行结束时,gc自动析构,将单例对象释放掉,这一过程也不是线程来干的,所以也是线程安全的,そして,至于pthread_mutex? WTF。而因为无论存在多少Singleton对象,每次getInstance必然获取到的是同一个instance。
v7: 终极模式
等等,全部“非你莫属”的类,都要改写成单例模式么?NONONO,能不能只写一个单例模式,全部须要限制实例个数的类均可复用之?这样,当系统中须要多个不一样类的单例对象时,能够节约许多代码。固然能够作到,用模板就能够:
template <typename T> class Singleton { public: Singleton() {} Singleton(const Singleton &s) {} Singleton & operator = (const Singleton & s) {} public: static Singleton *getInstance() { return GC::pinstance; } class GC { public: static T *pinstance; GC() { pinstance = new T(); } ~GC() { if( pinstance ) { delete pinstance; pinstance = nullptr; } } }; GC gc; };
template <typename T> T * Singleton<T>::GC::pinstance = nullptr;
固然很显然,使用了模板的话,就须要一些约定俗成的东西了,例如在实例化的时候须要一个不带参数的构造函数T()。在使用时,对于用Singleton<T>::getInstance函数返回获得的对象指针,就是非你莫属的同一个指针了:
int main() {
Singleton<int> sgton; int *p1 = Singleton<int>::getInstance(); int *p2 = Singleton<int>::getInstance(); int *p3 = Singleton<int>::getInstance();
printf("%x\n", p1); cout <<(p1==p2)<<endl; cout <<(p2==p3)<<endl; return 0; }
以上测试代码的结果为0x........ 1 1.
ちょっと待って!!还不够!以上代码仍是有问题的!注意此时咱们要限制对象个数的不是Singleton对象了,而是T对象。所以设计目标是:无论申明多少个Singleton对象都是合法的,而无论申明了多少个Singleton对象,调用getInstance都只会获得一个T对象。
再看上面的代码,若是用户手贱声明了多个Singleton对象会怎么样呢?对于一个Singleton类的static对象,只要程序中的Singleton对象个数大于0,就会维护惟一的一个static变量。但是上面的static变量是个指针!!这就意味着每次构造Singleton对象时,都会调用GC构造函数中的new运算符,将一个新的T对象的地址赋值给pinstance!!那旧的那个呢?WHO CARES!!这样一来轻则致使内存泄漏,重则致使系统运行出错——若是先申明了一个Singleton对象,用着用着中间又声明出来一个Singleton对象,那么pinstance指向的内容就被偷换了!
说了这么多,为了防止发生这样的事情,只须要在new的时候和delete的时候稍稍注意一下便可,为了防止new两次,加上if语句;为了防止delete两次(固然在其余场合也同样),养成delete以后就将指针赋为空,以避免指针在其余地方被误引用。
template <typename T> class Singleton { public: Singleton() {} Singleton(const Singleton &s) {} Singleton & operator = (const Singleton & s) {} public: static T *getInstance() { return GC::pinstance; } class GC { public: static T *pinstance; GC() { if( !pinstance ) pinstance = new T(); } ~GC() { if( pinstance ) { delete pinstance; pinstance = nullptr;
} } }; static GC gc; }; template <typename T> T * Singleton<T>::GC::pinstance = nullptr;
这样一来,只要在起子线程以前声明一个Singleton对象,那么无论后面是main线程仍是子线程声明了其余的Singleton对象,T对象都不会出现第二个。
反思:
1 为何Java用内部类维护单例?Java这么作是为了在不写synchronized关键字(减小锁的代价)的状况下,采用延时加载的方式实现单例。怎么作到不用锁而作到线程安全呢?在Java中,内部类都只会加载一次,所以内部类的初始化过程有且仅有一次,而这个过程是JVM干的,不受用户多线程的影响。为何是延时加载呢?只有getInstance()用到了内部类的时候,才会加载内部类。
2 为何C++写法和Java延时加载且线程安全的单例写法不同呢?C++是不会加载内部类的,只有当有对象须要实例化时,才调用其构造函数进行初始化,同时也初始化内部类的static变量。若是像Java同样只是定义一个内部类而不去实例化它,是不行的。所以按照上面的写法,使用该单例模板类必须先定义一个Singleton对象,这时候因为内部类做为Singleton的成员,会先被构造,“顺便地”就正确地new出了一个T对象,pinstance指针是static变量,一个类最多只有一个,因此不会有两个pinstance,那么只要再内部类的构造中防止new出两个T对象,就能够保证进程中有惟一的pinstance指向惟一的T。
to be continue