zoukankan      html  css  js  c++  java
  • Singleton模式(单例模式)

    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的栈中保存的指针会被调用,可以保证析构函数被执行。

  • 相关阅读:
    送给热爱书法的朋友们
    [原创]中秋随笔 祝大家中秋快乐
    Comsenz力邀您的加盟
    夜半冻醒有感
    Comsenz力邀您的加盟
    成熟的谷子先低头
    [转载]10个经典的web2.0配色方案网站
    无法嵌入互操作类型“Microsoft.Office.Interop.Excel.ApplicationClass”
    C#获取真实IP地址及分析
    使用TRY CATCH进行SQL Server异常处理
  • 原文地址:https://www.cnblogs.com/cyy-13/p/5718607.html
Copyright © 2011-2022 走看看