天天看点

设计模式解析和实现(C++, java)之十-singleton模式

 单例模式可以保证一个类有且只有一个实例,并提供一个访问它的全局访问点.在程序设计中,有很多情况需要确保一个类只能有一个实例.从这句话可以 看出,Singleton模式的核心:如何控制用户使用new对一个类的实例构造器的任意调用。如何绕过常规的构造器,提供一种机制来保证一个类只有一个 实例?这应该是类设计者的责任,而不是使用者的责任。

 一、单例模式意图

  保证一个类有且只有一个实例,并提供一个访问它的全局访问点。

 二、单例模式UML图

图1: Singleton 模式结构

设计模式解析和实现(C++, java)之十-singleton模式

实现 Singleton 模式的办法通常有三种:

  1.用静态方法实现 Singleton;

  2.以静态变量为标志实现 Singleton;

  3.用注册器机制来创建 Singleton。

三 实现Java Singleton的方案

Java Singleton是指在特定系统范围内只能实例化一次的Java类,如何理解特定系统范围?按照需求和环境定义系统范围,关注在特定系统范围内单一实例的需求:

系统范围和环境定义 方案编号
框架容器内 A
单一JVM中、单一类加载器加载类 B、C、D
单一JVM、不同类加载器加载相同类
系统跨多个JVM

A

提供实例管理容器的第三方框架,例如Spring IOC容器,可以通过配置保证实例在容器内的唯一性。这些被外部容器管理的类,能在某个容器范围内达到Singleton的效果,不一定禁止自身在容器外 部范围生成多个实例。 这种方案可以看作是一种局部的单例解决方案。

B

代码示范如下

设计模式解析和实现(C++, java)之十-singleton模式

C

设计模式解析和实现(C++, java)之十-singleton模式

D

有时候类的实例化开销较为昂贵,有时候类的实例化要用到系统运行时的动态数据做参数等等,在上述情形,静态成员变量需要延迟初始化。程序员中间流传较广的一种形式是:

设计模式解析和实现(C++, java)之十-singleton模式

这种写法最大的问题是getInstance()方法被多个线程竞争使用的时候,可能会产生多个实例,违反了单例设计的初衷。如果多个实例的风险(Singleton失败)不会引起您的系统异常,比如实例存放的是无状态的数据、实例是轻量级的,您可以坚持这种写法。

当然还有改进的方法,一般是在该方法前加上“synchronized”关键字:

设计模式解析和实现(C++, java)之十-singleton模式

这种作法的副作用就是影响性能。

双检查锁(Double-Checked Locking)是在多线程环境下实现延迟初始化的有效方式(如C++),不幸的是,对大多数JVM是无效的。有一篇文章解释的很深入:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

其他方案

单一JVM、不同类加载器加载相同类或跨多个JVM的情形,要保证Java Singleton不失败,我还没找到恰当的方法。

有些人认为单例的需求不仅仅是来自某些组件自身的唯一特性,还来自对创建并维护多个对象产生消耗的无法忍受、来自对性能的追求。如果出于降低消耗和 提高性能的目的,很多无状态的类(类的所有实例天然是始终相同的),即便面临单一JVM、不同类加载器加载相同类或跨多个JVM的情形,也可以采用上述的 Singleton实现方式,可以尽量减少实例的数量。

当Java Singleton遇到反序列化

一个序列化的实例,每次反序列化的时候都会产生一个新的实例。Singleton也不例外。

我们看看一个例子:

设计模式解析和实现(C++, java)之十-singleton模式

进行序列化测试

public static void main(String[] args) throws Exception{

         //序列化

          ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("E://kingBedroom.obj"));

          King king_1 = King.INSTANCE;         

          objectOutputStream.writeObject(king_1);

          objectOutputStream.close();

          //反序列化

          ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("E://kingBedroom.obj"));

          King king_2 = (King)objectInputStream.readObject();

          objectInputStream.close();

          //比较是否原来的实例

          System.out.println(king_1==king_2);

    }

结果是false

解决方法是为King类增加readResolve()方法:

private Object readResolve(){

        return INSTANCE;

    }

反序列化之后新创建的对象会先调用此方法,该方法返回的对象引用被返回,取代了新创建的对象。本质上,该方法忽略了新建对象,仍然返回类初始化时创建的那个实例。

五:C++的设计与实现:

C++完美实现Singleton模式

Singleton 模式是常用的设计模式之一,但是要实现一个真正实用的设计模式却也不是件容易的事情。

1.         标准的实现

class Singleton

{

public:

       static Singleton * Instance()

       {

              if( 0 == _instance)

              {

                     _instance = new Singleton;

              }

              return _instance;

       }

protected:

       Singleton(void)

       {

       }

       virtual ~ Singleton(void)

       {

       }

       static Singleton* _instance;

};

       这是教科书上使用的方法。看起来没有什么问题,其实包含很多的问题。下面我们一个一个的解决。

2.         自动垃圾回收

上面的程序必须记住在程序结束的时候,释放内存。为了让它自动的释放内存,我们引入 auto_ptr 改变它。

#include <memory>

#include <iostream>

using namespace std;

class Singleton

{

public:

       static Singleton * Instance()

       {

              if( 0 == _instance. get())

              {

                     _instance. reset( new Singleton);

              }

              return _instance. get();

       }

protected:

       Singleton(void)

       {

              cout << "Create Singleton" << endl;

       }

       virtual ~ Singleton(void)

       {

              cout << "Destroy Singleton" << endl;

       }

       friend class auto_ptr< Singleton>;

       static auto_ptr< Singleton> _instance;

};

//Singleton.cpp

auto_ptr< Singleton> Singleton:: _instance;

3.         增加模板

在我的一个工程中,有多个的 Singleton 类,对 Singleton 类,我都要实现上面这一切,这让我觉得烦死了。于是我想到了模板来完成这些重复的工作。

现在我们要添加本文中最吸引人单件实现:

#pragma once

#include <memory>

using namespace std;

using namespace C2217:: Win32;

namespace C2217

{

namespace Pattern

{

template <class T>

class Singleton

{

public:

       static inline T* instance();

private:

       Singleton(void){}

       ~ Singleton(void){}

       Singleton(const Singleton&){}

       Singleton & operator= (const Singleton &){}

       static auto_ptr< T> _instance;

};

template <class T>

auto_ptr< T> Singleton< T>:: _instance;

template <class T>

  inline T* Singleton< T>:: instance()

{

       if( 0 == _instance. get())

       {

              _instance. reset ( new T);

       }

       return _instance. get();

}

//Class that will implement the singleton mode,

//must use the macro in it's delare file

#define DECLARE_SINGLETON_CLASS( type ) /

       friend class auto_ptr< type >; /

       friend class Singleton< type >;

}

}

4.         线程安全

上面的程序可以适应单线程的程序。但是如果把它用到多线程的程序就会发生问题。主要的问题在于同时执行 _instance. reset ( new T); 就会同时产生两个新的对象,然后马上释放一个,这跟 Singleton 模式的本意不符。所以,你需要更加安全的版本:

#pragma once

#include <memory>

using namespace std;

#include "Interlocked.h"

using namespace C2217:: Win32;

namespace C2217

{

namespace Pattern

{

template <class T>

class Singleton

{

public:

       static inline T* instance();

private:

       Singleton(void){}

       ~ Singleton(void){}

       Singleton(const Singleton&){}

       Singleton & operator= (const Singleton &){}

       static auto_ptr< T> _instance;

       static CResGuard _rs;

};

template <class T>

auto_ptr< T> Singleton< T>:: _instance;

template <class T>

CResGuard Singleton< T>:: _rs;

template <class T>

  inline T* Singleton< T>:: instance()

{

       if( 0 == _instance. get() )

       {

              CResGuard:: CGuard gd( _rs);

              if( 0 == _instance. get())

              {

                     _instance. reset ( new T);

              }

       }

       return _instance. get();

}

//Class that will implement the singleton mode,

//must use the macro in it's delare file

#define DECLARE_SINGLETON_CLASS( type ) /

       friend class auto_ptr< type >; /

       friend class Singleton< type >;

}

}

       CresGuard 类主要的功能是线程访问同步 , 代码如下:

#pragma once

///

// Instances of this class will be accessed by multiple threads. So,

// all members of this class (except the constructor and destructor)

// must be thread-safe.

class CResGuard {

public:

   CResGuard()   { m_lGrdCnt = 0 ; InitializeCriticalSection(& m_cs); }

   ~ CResGuard() { DeleteCriticalSection(& m_cs); }

   // IsGuarded is used for debugging

   BOOL IsGuarded() const { return( m_lGrdCnt > 0 ); }

public:

   class CGuard {

   public:

      CGuard( CResGuard& rg) : m_rg( rg) { m_rg. Guard(); };

      ~ CGuard() { m_rg. Unguard(); }

   private:

      CResGuard& m_rg;

   };

private:

   void Guard()    { EnterCriticalSection(& m_cs); m_lGrdCnt++; }

   void Unguard() { m_lGrdCnt--; LeaveCriticalSection(& m_cs); }

   // Guard/Unguard can only be accessed by the nested CGuard class.

   friend class CResGuard:: CGuard;

private:

   CRITICAL_SECTION m_cs;

   long m_lGrdCnt;    // # of EnterCriticalSection calls

};

///

5.         实用方法

比如你有一个需要实现单件模式的类,就应该这样实现:

#pragma once

#include "singleton.h"

using namespace C2217:: Pattern;

class ServiceManger

{

public:

       void Run()

       {

       }

private:

       ServiceManger(void)

       {

       }

       virtual ~ ServiceManger(void)

       {

       }

       DECLARE_SINGLETON_CLASS( ServiceManger);

};

typedef Singleton< ServiceManger> SSManger;

在使用的时候很简单,跟一般的 Singleton 实现的方法没有什么不同。

int _tmain(int argc, _TCHAR* argv[])

{

        SSManger:: instance()-> Run();

}