關於C#的Lock關鍵字實現線程安全的單例模式

在看pureMVC使用的時候注意到有個關於線程安全的單例模式的使用用到了Lock關鍵詞,這裏對其進行一下記錄。代碼如下,


protected static readonly object m_staticSyncRoot;

public new static IFacade Instance
    {
        get
        {
            if(m_instance == null)
            {
                lock(m_staticSyncRoot)
                {
                    if (m_instance == null)
                    {
                        Debug.Log("ApplicationFacade");
                        m_instance = new ApplicationFacade();
                    }
                }
            }
            return m_instance;
        }
    }

這裏用到了一個關鍵詞lock,lock通過鎖定一個實例,對一個代碼段進行單線程的訪問控制,即不論在任何時候只能有一個線程對lock大括號的代碼段進行訪問,其他線程運行到這個代碼段的時候只能等待(或者說阻塞)在進入lock之前。
lock鎖定一個對象的原理如下:

  • 對於任何一個對象,其在內存中的第一部分放置的是所有方法的地址,第二部分放着一個索引,索引指向CLR(公共語言運行時)中的SyncBlock
    Cache區域中的一個SyncBlock(SyncBlock簡單來說就是Monitor.Enter用來鎖定一個實例當前僅能被一個線程訪問的工具).
  • Monitor.Enter和Monitor.Exit,這倆個函數是c#庫的函數,他們的作用分別是鎖定一個資源,使得其他線程不能被訪問,以及解鎖這個資源。當執行Monitor.Enter(object)時,如果object的索引值爲負數,就從SyncBlock Cache中選區一個SyncBlock,將其地址放在object的索引中。這樣就完成了以object爲標誌的鎖定,其他的線程想再次進行Monitor.Enter(object)操作,將獲得object爲正數的索引,然後就等待。直到索引變爲負數,即當前鎖定線程使用Monitor.Exit(object)將object的索引變爲負數。
  • lock語句根本使用的是Monitor.Enter和Monitor.Exit,即在大括號開始時對lock操作的對象傳進Monitor.Enter,這裏是m_staticSyncRoot,在大括號結束的時候再使用Monitor.Ente對m_staticSyncRoot進行解鎖。

在上述代碼中,lock的外部和內部使用到了兩個看似重複的if判斷,兩者實際上都是有各自的功能的。
lock外部的if主要簡單的對m_staticSyncRoot進行判空判斷,決定是否要進入lock語句塊。
在上文中黑體字突出的等待中可以發現,如果有多個線程同時執行上述代碼,第一個線程進入lock塊進行操作時,其餘的線程都是在進入第一個if後進入lock之前等待,第一個進入lock塊的線程進行初始化結束之後,其實這時m_staticSyncRoot已經指向某個實例了, 第二個進來的線程如果不進行判斷,則還會再次進行實例化,所以在lock裏面的if實際上是有用的。

關於lock的使用注意事項:

  • lock的對象不能是null,關於lock對一個對象進行上鎖的機理中可以發現需要訪問對象的內存的第二部分,而null值什麼都沒有,所以用null進行lock會報錯。
  • 在使用lock的時候,被lock的對象(locker)一定要是引用類型的(即類的實例,在內存堆中生成,需要由垃圾回收機制進行銷燬),如果是值類型(如int類型等基本類型,在內存棧中生成,使用完即刻銷燬),將導致每次lock的時候都會將該值類型的對象裝箱爲一個新的引用對象(事實上如果使用值類型,C#編譯器(3.5.30729.1)在編譯時就會給出一個錯誤)。
  • 不要使用lock(this),this指的是當前的實例,當鎖住後導致別的進程也無法訪問整個實例,應lock一個專門用於lock的私有或者保護類型的成員變量或者私有或者保護類型的靜態變量
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章