设计模式(2) - Singleton单件模式

来源:互联网 发布:建行网络贷款怎么过 编辑:程序博客网 时间:2024/05/28 09:33
  最近项目里边用到了ACE, 里边的很多类都使用了ACE_Singleton。单件模式的原理虽然简单,易懂,但感觉要用好它,也不是那么容易。下面从几个方面来进行说明。

  1. 基本实现

  下面代码是单件模式的最基本的实现,仅限于单线程环境。 通过定义一个静态的成员,来保存这个唯一的对象实例。并通过一个静态接口来获取这个唯一的实例。
class Singleton{public:~Singleton(){cout<<"Singleton Destructor"<<endl;}static Singleton* getInstance(){if(NULL==_instance){_instance = new Singleton();}return _instance;}private:Singleton(){cout<<"Singleton Constructor"<<endl;};static Singleton* _instance;};Singleton* Singleton::_instance = NULL;int main(){Singleton* sgn1 = Singleton::getInstance();Singleton* sgn2 = Singleton::getInstance();if(sgn1 == sgn2)cout<<"unique instance"<<endl;system("pause");return 0;}
  运行结果为:
  Singleton constructor
  unique instance
  Press any key to continue . . .

  2. 静态成员的释放

  有了new, 就应该有对应的delete. 那到底什么时候释放呢。因为new分配的是堆内存,估计即使加上析构函数,也没有效果。带着这个疑问,做了下实验,给类Singleton加上了一个析构函数。
Singleton::~Singleton(){      std::cout<<"Singleton destructor"<<std::endl;}
  实验结果显示推测正确,堆内存不会自动释放。
  自然的,会想到定义一个释放函数的方法,调用delete Singleton::instance()对静态实例进行释放。但是,这样增加了风险,因为调用者很可能会忘记调了此函数。
  查了下资料,得到了下面的几种方法:

  2.1. 使用内嵌类

  通过一个内嵌类和一个静态成员来实现自动释放的机制,相当于为单件加了个垃圾回收器。关键点在于static Cleaner clr;这个声明,由于是静态成员,系统会在栈里分配内存,回收工作也就由系统自动完成了。
class Singleton{public:~Singleton(){cout<<"Singleton Destructor"<<endl;}static Singleton* getInstance(){if(NULL==_instance){_instance = new Singleton();static Cleaner clr;}return _instance;}private:Singleton(){cout<<"Singleton Constructor"<<endl;};static Singleton* _instance;class Cleaner{public:Cleaner(){cout<<"Cleaner Constructor"<<endl;}~Cleaner(){cout<<"Cleaner Destructor"<<endl;if(NULL!=Singleton::_instance)delete Singleton::_instance;}};};Singleton* Singleton::_instance = NULL;int main(){Singleton* sgn1 = Singleton::getInstance();Singleton* sgn2 = Singleton::getInstance();if(sgn1 == sgn2)cout<<"unique instance"<<endl;system("pause");return 0;}
  运行结果为:
  Singleton Constructor
  Cleaner Constructor
  unique instance
  Press any key to continue . . . 

  按任意键退出后,观察到有下面打印:
  Cleaner Destructor
  Singleton Destructor

  2.2. 使用局部静态变量

  前面提到的方法,需要新增一个嵌套类,增加了复杂度。
  可以对第1节(基本实现)中的getInstance方法进行优化,返回一个局部静态变量。

 static Singleton* getInstance() {static Singleton instance;return &instance; }
  程序在结束的时候,系统会自动回收所有的静态/全局内存,这样,单件模式中的静态成员就能被析构掉了。

  3. 线程安全问题

  3.1 问题起源

  所谓线程安全,就是说如果代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
  基于第1节中的基本实现,来验证下它是否线程安全:

#include<Windows.h>#include<process.h>#include<iostream>using namespace std;class Singleton{public:~Singleton(){cout<<"Singleton Destructor"<<endl;}void DoSomething(){cout<<"Singleton DoSomething"<<endl;}static Singleton* getInstance(){if(NULL == _instance)_instance = new Singleton();return _instance;}private:Singleton(){static int count = 0;cout<<"Singleton Constructor:"<<++count<<endl;};static Singleton* _instance;};unsigned __stdcall thread( void* ){Singleton* p = Singleton::getInstance();p->DoSomething();return 0;}Singleton* Singleton::_instance = NULL;int main( int argc, void* argv[] ){for( int i = 0; i < 10; ++i ){HANDLE t = (HANDLE)_beginthreadex( NULL, 0, thread, NULL, 0, NULL );CloseHandle(t);}system("pause");return 0;}
  运行结果为:
  Singleton Constructor:1Singleton Constructor:8
  Singleton Constructor:4
  Singleton DoSomething
  Singleton Constructor:9
  Singleton DoSomething
  Singleton DoSomething
  Singleton Constructor:5
  Singleton Constructor:3
  Singleton DoSomething
  Singleton Constructor:2
  Singleton Constructor:10
  Singleton DoSomething
  Singleton DoSomething
  Singleton DoSomethPress any key to continue . . . ing
  Singleton DoSomething
  Singleton Constructor:6
  Singleton DoSomething
  Singleton Constructor:7
  Singleton DoSomething
  从上面结果可以看到,前面的实现方案是非线程安全的。构造函数被调用了多次,生成了多个实例。

  3.2. 线程安全实现方案

  3.2.1 Lazy initialization

  也称为慢初始化方法。即第一次调用该类实例的时候才产生一个新的该类实例,并在以后仅返回此实例。
  此种方法,需要用锁来保证其线程安全性。因为如上3.1所示,多个线程可能会同时进入到判断实例是否存在的if语句中,它是非线程安全的。
  方案一:Double-Check
  可以使用double-check来保证thread safety,这也是ACE_Singleton采用的方法。但是如果处理大量数据时,该锁可能会成为严重的性能瓶颈。
  实现原理如下:

  Singleton* Singleton::getInstance()  {    if(NULL == _instance)    {      Lock();      if(NULL == _instance)        _instance = new Singleton();      Unlock();    }    return _instance;  }
 基于这个原理,重新实现了单件模式的基本代码,加入了线程互斥机制:
#include<Windows.h>#include<process.h>#include<iostream>using namespace std;class Singleton{public:~Singleton(){cout<<"Singleton Destructor"<<endl;}void DoSomething(){cout<<"Singleton DoSomething"<<endl;}static Singleton* getInstance(){if(NULL == _instance){WaitForSingleObject(mtx, INFINITE);if(NULL == _instance)_instance = new Singleton();ReleaseMutex(mtx);}return _instance;}static HANDLE mtx;private:Singleton(){static int count = 0;cout<<"Singleton Constructor:"<<++count<<endl;};static Singleton* _instance;};unsigned __stdcall thread( void* ){Singleton* p = Singleton::getInstance();p->DoSomething();return 0;}Singleton* Singleton::_instance = NULL;HANDLE Singleton::mtx=INVALID_HANDLE_VALUE;int main( int argc, void* argv[] ){Singleton::mtx = CreateMutex(NULL, FALSE, (LPCWSTR)"Mutex");if(NULL==Singleton::mtx)return 0;for( int i = 0; i < 10; ++i ){HANDLE t = (HANDLE)_beginthreadex( NULL, 0, thread, NULL, 0, NULL );CloseHandle(t);}Sleep(100);CloseHandle(Singleton::mtx);system("pause");return 0;}
  运行结果:
Singleton Constructor:1
Singleton DoSomething
Singleton DoSomething
Singleton DoSomething
Singleton DoSomething
Singleton DoSomething
Singleton DoSomething
Singleton DoSomething
Singleton DoSomething
Singleton DoSomething
Singleton DoSomething
Press any key to continue . . .
  方案二:内部静态实例
  此方法,就是2.2中所介绍的方法的改进。实现原理如下:
  Singleton* Singleton::getInstance()
  {
    Lock();
    static Singleton instance;
    Unlock();
    return &instance;
  }
  这里需要注意的是,C++0x以后,要求编译器保证内部静态变量的线程安全性,可以不加锁。但C++0x以前,仍需要加锁。这里是无锁版本:

#include<Windows.h>#include<process.h>#include<iostream>using namespace std;class Singleton{public:~Singleton(){cout<<"Singleton Destructor"<<endl;}void DoSomething(){cout<<"Singleton DoSomething"<<endl;}static Singleton* getInstance(){static Singleton instance;return &instance;}private:Singleton(){cout<<"Singleton Constructor"<<endl;};static Singleton* _instance;};unsigned __stdcall thread( void* ){Singleton* p = Singleton::getInstance();p->DoSomething();return 0;}Singleton* Singleton::_instance = NULL;int main( int argc, void* argv[] ){for( int i = 0; i < 5; ++i ){HANDLE t = (HANDLE)_beginthreadex( NULL, 0, thread, NULL, 0, NULL );CloseHandle(t);}Sleep(10);system("pause");return 0;}
  运行结果为:
  Singleton ConstructorSingleton DoSomething
  Singleton DoSomething

  Singleton DoSomething
  Singleton DoSomething
  Singleton DoSomething
  Press any key to continue . . .

  3.2.2 Eager Initialization

  也称为急切初始化。即无论是否调用该类的实例,在程序开始时就会产生一个该类的实例,并在以后仅返回此实例。
  静态初始化实例保证了其线程安全性。因为静态实例初始化在程序开始时进入主函数之前就由主线程以单线程方式完成了初始化,不必担心多线程问题。
  在性能需求较高时,可以使用这种模式,避免频繁的锁争夺。实现如下:

#include<Windows.h>#include<process.h>#include<iostream>using namespace std;class Singleton{public:~Singleton(){cout<<"Singleton Destructor"<<endl;}void DoSomething(){cout<<"Singleton DoSomething"<<endl;}static Singleton* getInstance(){if(NULL == _instance)_instance = new Singleton();return _instance;}private:Singleton(){cout<<"Singleton Constructor"<<endl;};static Singleton* _instance;};unsigned __stdcall thread( void* ){Singleton* p = Singleton::getInstance();p->DoSomething();return 0;}Singleton* Singleton::_instance = new Singleton;int main( int argc, void* argv[] ){for( int i = 0; i < 5; ++i ){HANDLE t = (HANDLE)_beginthreadex( NULL, 0, thread, NULL, 0, NULL );CloseHandle(t);}Sleep(10);system("pause");return 0;}
  运行结果如下:
  Singleton Constructor
  Singleton DoSomething
  Singleton DoSomething
  Singleton DoSomething
  Singleton DoSomething
  Singleton DoSomething
  Press any key to continue . . .

 4. 总结

  使用单件模式时,需要注意的几个方面:
  4.1 相互引用
  任意两个 Singleton 类的构造函数不能相互引用对方的实例, 否则会导致程序崩溃. 如:

  SingletonA& SingletonA::Instance() {    const SingletonB& b = SingletonB::Instance();    static SingletonA theSingleton;    return theSingleton;  }  SingletonB& SingletonB::Instance() {    const SingletonA & b = SingletonA::Instance();    static SingletonB theSingleton;    return theSingleton;  }
  4.2 多线程环境下
  在多线程的应用场合下必须小心使用. 如果唯一实例尚未创建时, 有两个线程同时调用创建方法, 且它们均没有检测到唯一实例的存在, 便会同时各自创建一个实例, 这样就有两个实例被构造出来, 从而违反了单例模式中实例唯一的原则. 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁 (虽然这样会降低效率).
  4.3 
  多个 Singleton 实例相互引用的情况下, 需要谨慎处理析构函数. 如: 初始化顺序为 SingletonA » SingletonB » SingletonC 的三个 Singleton 类, 其中 SingletonA SingletonB 的析构函数调用了 SingletonC 实例的成员函数, 程序退出时, SingletonC 的析构函数 将首先被调用, 导致实例无效, 那么后续 SingletonA SingletonB 的析构都将失败, 导致程序异常退出.

1 0
原创粉丝点击