2012年7月27日 星期五

Design Pattern: Singleton in JAVA, C#, C++

定義:
         保證一個class只有一個實體(Instance),並為它提供一個全域的訪問點(global access point)。

有的時候我們希望某個Class只會有一個Instance被產生。例如,Android中如果多個thread去access同一個DB就會產生錯誤。因此我們可能會希望透過一個Instance統一由他去Access 同一個DB。

JAVA中實作的方式如下:
public class Singleton {
    private volatile Singleton instance = null;
        public Singleton getInstance() {
            if (instance == null) {
                synchronized(this) {
                    if (instance == null)
                        instance = new Singleton();
                }
            }
            return instance;
        }
    }

深入探討:
為了避免multi-thread 的race condition發生及效率,這裡使用double check locking。避免race condtion的發生所以使用synchronized()。但是synchronized()可能造成performance的低落。因此在外層先判斷如果真的是null才進入critical section否則直接return。


在JDK 4(包含)以下的版本不支援volatile。volatile保證再每次取此變數的值時會去memory抓取而非從cache讀取。不加volatile有可能導致程式碼執行順序被 re-order。進而發生其他thread執行實可能看到非null的instance(但他這個時候明明是null)。

另外,上述的作法是lazy-Initialization的作法。也就是說當真正需要的時候才會被new出來。但是如果我們很確定程式中一定會去new這個物件,其實先把他new 出來也不會有什麼損失。


Singleton without Lazy-Initialization
public class Singleton {
    private volatile Singleton instance = new Singleton();
        public Singleton getInstance() {
            return instance;
        }
    }

這樣做的另外一個好處 程式碼看起來也比較清爽。
_____________________________________________________________________________________________________________


C#方式如下:
using System;

public sealed class Singleton
{
   private static volatile Singleton instance;
   private static object syncRoot = new Object();

   private Singleton() {}

   public static Singleton Instance
   {
      get 
      {
         if (instance == null) 
         {
            lock (syncRoot) 
            {
               if (instance == null) 
                  instance = new Singleton();
            }
         }

         return instance;
      }
   }
}

在上面的code中,使用sealed來避免被繼承進而發生可能產生多個instance。另外使用lock的方法來避免multi-thread的race condition。如同在JAVA說的,volatile可以避免out of order所造成的問題。另外,如果不需要lazy-initialization我們可以這樣寫:
public sealed class Singleton
{
   private static readonly Singleton instance = new Singleton();
   
   private Singleton(){}

   public static Singleton Instance
   {
      get 
      {
         return instance; 
      }
   }
}


使用readonly來保證instance只會在static initialization過程時或是在class constructor裡被建立。
_____________________________________________________________________________________________________________


C++的實作就比較簡單了
class Singleton {
public: 
    static Singleton* Instance(){
    if (_instance == 0) {
        _instance = new Singleton;
    }
    return _instance;
}
protected: 
    Singleton();
private:
    static Singleton* _instance=0;
}

然而這個作法不是thread safe的。在C++使用如上面提到的Double checked locking方法及使用volatile並不能保證out of order的問題。也就是說在C++上面使用volatile並無法像C#/JAVA上面來保證使用volatile的variable 不會被re-order。因此像下面的DCL的code還是無法保證Thread-Safe。
class  Singleton {  
public :  
    static  Singleton* Instance() {  
        Lock lock;  
        if  (_instance == 0) {  
            _instance =  new  Singleton;  
        }  
        return  _instance;  
    }  
private :  
    static  Singleton *  volatile  _instance;  
    Singleton(){  
    }  
};  

最後看到Solstice使用pthread解決這問題,如下:

#include 
template<typename T>
class Singleton : boost::noncopyable
{
public:
    static T& instance()
    {
        pthread_once(&ponce_, &Singleton::init);
        return *value_;
    }
    static void init()
    {
        value_ = new T();
    }
private:
    static pthread_once_t ponce_;
    static T* value_;
};
template<typename T>
pthread_once_t Singleton<T>::ponce_ = PTHREAD_ONCE_INIT;
template<typename T>
T* Singleton<T>::value_ = NULL;

Reference:
                 Gossip@caterpillar Design Pattern: Simple Factory 模式
                 The "Double-Checked Locking is Broken" Declaration
                 MSDN:Implementing Singleton in C#
                 多线程服务器的常用编程模型

沒有留言: