Singleton模式(单例模式)
要点:

  1. 实质和特点
  2. 实现手法:JAVA、C++不一样,实现时应该注意那些细节。
  3. 生命周期的控制:Dead Reference的解决
  4. 多线程问题:C++:双检锁、Volatile,
                            JAVA:JAVA中有 lazy initialization hoder 来实现

应用场景:
数据库连接
打印机(可以有几个打印任务,但只能有一个打印机)
序列号生成(多个的话可能会导制重复)
数据库表中记录ID的生成(非单例的话可能会重复)

单例模式的特点:
         单例类只能有一个实例。
         单例类必须自己创建自己的唯一实例。
         单例类必须给所有其它对象提供这一实例。
即:
    一个私有构造函数——确保用户无法通过new直接实例它;
    一个静态私有成员变量instance;
    一个静态公有方法Instance()——方法负责检验并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。并且这也是一个全局访问点。
例如:
class Singleton
{
public:
    static Singleton *Instance();  // 单例操作
private:
    Singleton();//关闭缺省构造函数
    Singleton(const Singleton&);//关闭拷贝构造函数
    Singleton& operator=(const Singleton&);//关闭赋值运算符
    ~Singleton();//避免被外界delete
    static Singleton *m_Instance;  // 单例指针
};
 实质:
单例对象的类必须保证只有一个实例存在
单例模式应用:
         每台计算机可以有若干个打印机,但只能有一个Printer Spooler,避免两个打印作业同时输出到打印机。
         一个具有自动编号主键的表可以有多个用户同时使用,但数据库中只能有一个地方分配下一个主键编号。否则会出现主键重复。
Singleton模式的结构:

注:Singleton模式包含的角色只有一个,就是Singleton。Singleton拥有一个私有构造函数,确保用户无法通过new直接实例它。除此之外,该模式中包含一个静态私有成员变量instance与静态公有方法Instance()。Instance方法负责检验并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。
—————————————————————————————————————————————————————————————————
          (http://blog.csdn.net/benny5609/article/details/2438922)
          Design Pattern无疑是每个程序员都应该阅读的一本书,这本书给出了23个Pattern,其中最简单的就是Singleton Pattern了,这里,我大概介绍一下自己使用Singleton时曾经用到过的一些做法,希望对大家有些帮助。
     意图
     Single Pattern的主要是为了保证类仅有一个实例,并保证提供一个访问它的全局访问点
     适用情况??
     一个对象的行为取决于它的状态,并且它必须在运行时改变它的状态
     一个操作包含庞大的Switch&Case语句
     优点??
     它将和特定状态相关的行为局部化了,这样就可以通过定义新的子类来增加新的状态和转换。
做法1:
因为在书中并没有提及如何删除m_pInstance指针,所以就提供exitInstance()方法手动删除
class CSingleton  
{
public:
   virtual ~CSingleton();
  static CSingleton* instance(void)
  {
       if(NULL == m_pInstance)
               return m_pInstance = new CSingleton;
       return m_pInstance;
   }
   void exitInstance(void)
   { delete m_pInstance;}
private
    CSingleton();
    static T* m_pInstance;
}
CSingleton* CSingleton::m_pInstance = NULL;
 
做法2:
在类中增加一个嵌套类,让这个嵌套类负责删除m_pInstance(类似于auto_ptr的原理)
class CSingleton
{
public:
    virtual ~CSingleton();
    static CSingleton* Instance()
    {
        if(NULL == m_pInstance)
              m_pInstance = new CSingleton;
        return m_pInstance;
    }
private:
    CSingleton();
    static CSingleton* m_pInstance;
    class Cleaner
    {
    public:
         ~Cleaner()
         { delete m_pInstance;}
    }
    friend class CSingleton::Cleaner;
    static CSingleton::Cleaner cleaner;
}
CSingleton CSingleton::m_pInstance = NULL;
CSingleton::Cleaner CSingleton::cleaner;
 
做法3:
使用template的特性,建一个关于Singleton的template class,任何想使用Singleton Pattern 的类只需要从它这里继承就可以了
template
class CSingleton  
{
public:
 static T
instance(void)
 {
  static T instance;
  return &instance;
 }
protected:
 CSingleton(){};
 virtual ~CSingleton(){};
private:
    CSingleton(const CSingleton& source){};
};
如果类CTest想使用Singleton特性
CTest : public CSingleton
{
    friend CSingleton;
    CTest();
public:
    ~CTest();
}
 
Singleton模式的C++实现研究(示例代码)
Singleton模式面临的两个问题:
多线程中的单例模式
单例模式的销毁
 Singleton是一种经过改进的全局变量,该模式的描述很简单,但实现却很复杂。特别是Singleton对象的生命周期管理是实现Singleton时最伤脑筋的地方。
  本文将讨论以下几个主题:
l  与单纯的全局对象相比,Singleton的特性
l  用以支持单实例的C++基本手法
l  资源泄漏问题与Meyers Singleton
l  Dead Reference问题
l  Phoenix Singleton
l  带寿命的Singleton
l  多线程问题
l  在动态库中使用Singleton

1.1.1  静态数据+静态函数 != Singleton
Singleton好像可以由静态成员变量+静态成员函数来取代?
例如:
Class Font{…};
Class PrintPort{…};
Class PrintJob {…};
Class MyOnlyPrinter
{
Public:
  Static void AddPrintJob(PrintJob& newJob)
{
  If(printQueue_.empty() && printingPort_.available())
{
  printingPort_.send(newJob.Data());
}
Else
{
  printQueue_.push(newJob);
}
}
Private:
  Static queue printQueue_;
  Static PrinterPort printingPort_;
  Static Font defaultFont_;
}
 
PrintJob somePrintJob(“MyDocument.txt”);
MyOnlyPrinter::AddPrintJob(somePrintJob);
 
上述代码有哪些缺点?
我认为主要缺点有两点:
n  代码的初始化和清理可能会有麻烦。
n  初始化工作在程序一启动时就已经完成,如果静态函数到程序退出时都没有调用,我们的初始化工作就白做了。
 
1.1.2  用以支持Singleton的一些C++基本手法
单实例的原始版实现代码:
class Singleton
{
public:
    static Singleton *Instance();  // 单例操作
private:
    Singleton();//关闭缺省构造函数
    Singleton(const Singleton&);//关闭拷贝构造函数
    Singleton& operator=(const Singleton&);//关闭赋值运算符
    ~Singleton();//避免被外界delete
    static Singleton *m_Instance;  // 单例指针
};
// 创建单例指针并初始化
Singleton *Singleton::m_Instance = NULL;
 
// 单例操作实现
Singleton *Singleton::Instance()
{
    if (NULL == m_Instance)
    {
        m_Instance = new Singleton();
    }
    return m_Instance;
}
 
// 构造函数
Singleton::Singleton()
{
}
 
// 析构函数
Singleton::~Singleton()
{
}
 
通过上述代码,Singleton对象的唯一性在编译期就已经实现,这正是C++实现Singleton设计模式的精髓所在。
 
除了使用New操作符动态分配内存来实现Singleton,我们还经常通过静态变量实例方式来实现这个模式,下面给出了一种用静态变量方式实现Singleton的代码。
class Singleton
{
public:
    static Singleton &Instance();    // 单例操作
private:
   Singleton();//关闭缺省构造函数
   Singleton(const Singleton&);//关闭拷贝构造函数
   Singleton& operator=(const Singleton&);//关闭赋值运算符
   ~Singleton();//避免被外界delete private:
   static Singleton m_Instance;     // 静态单例变量
};
// 创建单例
Singleton Singleton::m_Instance; 注意C++中静态变量的初始化必须在类外面!
 
// 单例操作实现
Singleton &Singleton::Instance()
{
    return m_Instance;
}
因为是类的静态变量,上面的代码在进程刚开始启动时就实例化了Singleton对象。
这有两个缺点:
n  初始化工作在程序一启动时就已经完成,如果静态函数到程序退出时都没有调用,我们的初始化工作就白做了;
n  初始化顺序无法确定,这可能会遇到麻烦,例如:

include “Singleton.h”

Int global = Singleton::Instance()->DoSomething();
由于无法保证编译器先初始化m_Instance,再初始化global全局变量,所以有可能在程序启动时就发生异常。
    因此,虽然上述实现简单,但建议抵制住这种诱惑。
 
1.1.3  Meyers Singleton
            对于上一节讨论的指针实现方法,我们有一个问题一直没有讨论,就是Singleton对象何时析构的问题:Singleton对象应该在何时摧毁自身实体?GoF著作中并没有讨论这个主题,事实上,这个问题确实很棘手。
            其实,就算Singleton未被删除,也不会造成内存泄露。此外,当一个进程终止时,所有现代操作系统都能够将进程所用到的内存完全释放。
然而,泄露可能还是存在的,而且更隐蔽更有害,那就是资源泄漏。这是因为Singleton的构造函数可以索求广泛的资源:网络连接,OS的内核对象,IPC方法中的各种句柄,数据库连接等。
避免资源泄漏的唯一正确方法是在程序关闭时期删除Singleton对象。
对于静态变量的初始化方法,有一个改进的版本,通过利用了编译器的奇特技巧,可以摧毁Singleton对象。
Singleton &Singleton::Instance()
{
    static Singleton m_Instance;     // 静态单例变量
    return m_Instance;
}
            这一手法是由Scott Meyers(Meyers 1996a,条款26)最先提出,所以被称为Meryars singleton。函数内声明的静态变量函数内的static对象,在函数第一次被调用时初始化。
编译器会产生一些代码,保证在进程退出前析构函数会被调用。实际上在编译器产生的代码中会调用atexit函数,把单实例的析构函数注册到atexit中,保证在进程退出前会调用这个析构函数。
Atexit函数内部维护了一个堆栈,先压入栈的函数指针会后执行。

如果是静态函数实现,则在程序推出时会调用静态函数的析构方法吗?
会!只要是静态实例,在程序推出后会调用析构函数。

如果是New出来的实例,而没有显式调用析构函数呢在退出时会调用析构函数吗? 
不会!如果是new出来的实例变量,如果没有显示的调用析构函数,在程序退出时并不会调用析构函数。因此,如果单例模式以此方式实现,则会产生泄露(像数据库连接、OS内核对像,网络连接等可能不会被正确关闭)

我们自己也可以显示的在代码中调用atexit,以下是实现代码。
class Singleton
{
    …
private:
    static void FreeInstance(void);  // 释放单例变量函数
};
// 单例操作实现
Singleton Singleton::Instance()
{
    if (NULL == m_Instance)
    {
        m_Instance = new Singleton();
        // 用系统函数atexit告诉系统在程序退出的时候调用FreeInstance
        atexit(FreeInstance);
    }
    return m_Instance;
}
 
// 释放单例变量函数
void Singleton::FreeInstance(void)
{
    if (NULL != m_Instance)
    {
        delete m_Instance;
        m_Instance = NULL;
    }
}
1.1.4  Dead Reference问题
            Meryers singleton是一种很好的Singleton的实现方式,大多数情况下,我们用这种方法实现Singleton就足够了,但有些情况下还是会有问题。
            举个例子,假设有个程序使用了三个Singletons:Keyboard,Display和Log。我们以Meryers singleton来实现这三个Singletons。假设Keyboard和Display先于Log实例化,那么按照atexit后进先出的原则,在进程退出时Log应该先被析构,假设Log析构完成后,Keyboard析构函数调用时发生错误,这时需要调用Log来记日志,但Log已经被析构了,所以程序会崩溃,这就是Dead Reference问题。
首先我们看一个简单的解决方案:
// Singleton.h
class Singleton
{
public:
Singleton& Instance()
{
if (!pInstance_)
{
// Check for dead reference
if (destroyed_)
{
OnDeadReference();
}
else
{
// First call—initialize
Create();
}
}
return pInstance_;
}
private:
// Create a new Singleton and store a
// pointer to it in pInstance_
static void Create();
{
// Task: initialize pInstance_
static Singleton theInstance;
pInstance_ = &theInstance;
}
// Gets called if dead reference detected
static void OnDeadReference()
{
throw std::runtime_error("Dead Reference Detected");
}
virtual ~Singleton()
{
pInstance_ = 0;
destroyed_ = true;
}
// Data
Singleton pInstance_;
bool destroyed_;
... disabled 'tors/operator= ...
};
// Singleton.cpp
Singleton
Singleton::pInstance_ = 0;
bool Singleton::destroyed_ = false;
 
当出现Dead Reference问题时,程序会直接抛出C++异常,这个方案廉价,简单并且不损失效率。
 
1.1.7  多线程问题
            前面的实现代码还没有考虑多线程问题,实际绝大部分情况,程序都是运行在多线程环境下的。
对Singleton加锁的代码可以像以下这样的简单方式:
Singleton& Singleton::Instance()
{
// mutex_ is a mutex object
// Lock manages the mutex
Lock guard(mutex_);
if (!pInstance_)
{
pInstance_ = new Singleton;
}
return *pInstance_;
}
这个好用,但效率不佳。
使用所谓的双检测锁定技术可以解决效率问题:
Singleton& Singleton::Instance()
{
if (!pInstance_) // 1
{
Guard myGuard(lock_);  // 2
if (!pInstance_) // 3
{
pInstance_ = new Singleton; // 4
}
}
return *pInstance_;
}
 
         即使使用双检测锁定技术,在实践中代码也不总是正确。有些时候编译器会对上述代码进行优化,从而改变代码执行顺序,导致锁定失效。
            为了避免编译器进行不必要的优化,需要在pInstance_声明前添加volatile修饰词。
 
1.1.8  在动态库中使用Singleton
            在动态库中使用Singleton,与动态库中的静态变量的生命周期相关,经过实践,可以得出结论,我们在前面讨论的Singleton技术在动态库下运行是没有问题的。
            在动态库被卸载的时候,atexit的栈中保存的指针会被调用,可以保证析构函数被执行。

posted on 2016-07-29 15:34  cyy_13  阅读(795)  评论(0编辑  收藏  举报