餓漢式單例與懶漢式單例的C#實現

Liuwei-Sunny發表於2012-12-02

      本文將介紹如何使用C#語言實現漢式單例漢式單例,並分析餓漢式單例與懶漢式單例的優缺點。

 

     1. 餓漢式單例類

      餓漢式單例類(Eager Singleton)是實現起來最容易的單例類,餓漢式單例類結構圖如圖1所示。

1 餓漢式單例類圖

      從圖1中可以看出,由於在定義靜態變數的時候例項化單例類,因此在類載入時單例物件就已建立,程式碼如下:

class EagerSingleton 
{ 
    private static EagerSingleton instance = new EagerSingleton(); 

    private EagerSingleton() { } 

    public static EagerSingleton GetInstance() 
    {
        return instance; 
    }
}

      當類被載入時,靜態變數instance 會被初始化,此時類的私有建構函式會被呼叫,單例類的唯一例項將被建立。

 

      2. 懶漢式單例類與雙重檢查鎖定

      與餓漢式單例類相同之處是,懶漢式單例類(Lazy Singleton)的建構函式也是私有的。與餓漢式單例類不同的是,懶漢式單例類在第一次被引用時將自己例項化,在懶漢式單例類被載入時不會將自己例項化。懶漢式單例類結構圖如圖2所示。

圖2  懶漢式單例類圖

      從圖2可以看出,在懶漢式單例類中,不是在定義靜態變數時例項化單例類,而是在第一次呼叫靜態工廠方法時例項化單例類。

      但是懶漢式單例存在一個很嚴重的問題:如果在高併發、多執行緒環境下實現懶漢式單例類,在某一時刻可能會有多個執行緒需要使用單例物件,即會有多個執行緒同時呼叫GetInstance()方法,可能會造成建立多個例項物件,這將違背單例模式的設計意圖。為了防止生成多個單例物件,需要使用C#語言中的lock關鍵字lock關鍵字鎖定的程式碼片段稱之為臨界區,可以確保當一個執行緒位於程式碼的臨界區時,另一個執行緒不能進入臨界區。如果其他執行緒試圖進入鎖定的程式碼,則將一直等待,直到該物件被釋放為止。修改之後的懶漢式單例類程式碼如下:

class LazySingleton 
{ 
    private static LazySingleton instance = null; 
    //程式執行時建立一個靜態只讀的輔助物件
    private static readonly object syncRoot = new object();

    private LazySingleton() { } 

    public static LazySingleton GetInstance() 
    { 
        //第一重判斷,先判斷例項是否存在,不存在再加鎖處理
        if (instance == null) 
        {
            //加鎖的程式在某一時刻只允許一個執行緒訪問
            lock(syncRoot)
            {
                //第二重判斷
                if(instance==null)
                {
                    instance = new LazySingleton();  //建立單例例項
                }
            }
        }
        return instance; 
    }
}

      在上面給出的懶漢式單例類實現程式碼中,對靜態工廠方法GetInstance()中建立單例物件的程式碼進行了加鎖,由於在呼叫時無法確定該單例物件是否已建立,因此需要使用輔助物件syncRoot來進行程式碼鎖定。為了不影響程式的效能,此處只鎖定建立單例物件的程式碼,並未鎖定整個方法。如果例項存在則直接返回,如果例項未建立則加鎖後再建立。

      為了更好地對單例物件的建立進行控制,此處使用了一種被稱之為雙重檢查鎖定(Double-CheckLocking)的雙重判斷機制。在雙重檢查鎖定中,當例項不存在且同時有兩個執行緒呼叫GetInstance()方法時,它們都可以通過第一重“instance==null”判斷,然後由於lock鎖定機制,只有一個執行緒進入lock中執行建立程式碼,另一個執行緒處於排隊等待狀態,必須等待第一個執行緒執行完畢後才可以進入lock鎖定的程式碼,如果此時不進行第二重“instance==null”判斷,第二個執行緒並不知道例項已經建立,將繼續建立新的例項,還是會產生多個單例物件,違背單例模式的設計思想,因此需要進行雙重檢查。

 

      3. 餓漢式單例類與懶漢式單例類比較

       餓漢式單例類在類被載入時就將自己例項化,它的優點在於無須考慮多個執行緒同時訪問的問題,可以確保例項的唯一性;從呼叫速度和反應時間角度來講,由於單例物件一開始就得以建立,因此要優於懶漢式單例。但是無論系統在執行時是否需要使用該單例物件,由於在類載入時該物件就需要建立,因此從資源利用效率角度來講,餓漢式單例不及懶漢式單例,而且在系統載入時由於需要建立餓漢式單例物件,載入時間可能會比較長。

      懶漢式單例類在第一次使用時建立,無須一直佔用系統資源,實現了延遲載入,但是必須處理好多個執行緒同時訪問的問題,特別是當單例類作為資源控制器,在例項化時必然涉及資源初始化,而資源初始化很有可能耗費大量時間,這意味著出現多執行緒同時首次引用此類的機率變得較大,需要通過雙重檢查鎖定等機制進行控制,這將導致系統效能受到一定影響。

 

      Java程式設計師可進一步閱讀:確保物件的唯一性——單例模式 (三)確保物件的唯一性——單例模式 (四)

 

  【作者:劉偉  http://blog.csdn.net/lovelion

相關文章