設計模式解析和實現(C++, java)之十-singleton模式

 單例模式可以保證一個類有且只有一個實例,並提供一個訪問它的全局訪問點.在程序設計中,有很多情況需要確保一個類只能有一個實例.從這句話可以 看出,Singleton模式的核心:如何控制用戶使用new對一個類的實例構造器的任意調用。如何繞過常規的構造器,提供一種機制來保證一個類只有一個 實例?這應該是類設計者的責任,而不是使用者的責任。

 一、單例模式意圖

  保證一個類有且只有一個實例,並提供一個訪問它的全局訪問點。

 

 二、單例模式UML圖

 


圖1: Singleton 模式結構
圖1: 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

代碼示範如下

B

C

C

D

有時候類的實例化開銷較爲昂貴,有時候類的實例化要用到系統運行時的動態數據做參數等等,在上述情形,靜態成員變量需要延遲初始化。程序員中間流傳較廣的一種形式是:

D_01

這種寫法最大的問題是getInstance()方法被多個線程競爭使用的時候,可能會產生多個實例,違反了單例設計的初衷。如果多個實例的風險(Singleton失敗)不會引起您的系統異常,比如實例存放的是無狀態的數據、實例是輕量級的,您可以堅持這種寫法。

當然還有改進的方法,一般是在該方法前加上“synchronized”關鍵字:

D_02

這種作法的副作用就是影響性能。

雙檢查鎖(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也不例外。

我們看看一個例子:

001

進行序列化測試

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++的設計與實現:

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 類,我都要實現上面這一切,這讓我覺得煩死了。於是我想到了模板來完成這些重複的工作。

現在我們要添加本文中最吸引人單件實現:

/********************************************************************

    (c) 2003-2005 C2217 Studio

    Purpose:    Implement singleton pattern

    History:

*********************************************************************/

#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 模式的本意不符。所以,你需要更加安全的版本:

/********************************************************************

    (c) 2003-2005 C2217 Studio

    Purpose:    Implement singleton pattern

    History:

*********************************************************************/

#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 類主要的功能是線程訪問同步 , 代碼如下:

/******************************************************************************

Module:  Interlocked.h

Notices: Copyright (c) 2000 Jeffrey Richter

******************************************************************************/

 

 

#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();

}

 

 

發佈了13 篇原創文章 · 獲贊 6 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章