Lock同步鎖淺析

定義:lock確保當一個線程位於代碼的臨界區時,另外一個線程不進入臨界區,若是其餘線程試圖進入鎖定的代碼,則它將一直等待(即被阻止),直到該對象被釋放。Monitor方法是靜態的,不須要生成Monitor類的實例就能夠直接調用它們,在.NET Framework中,每一個對象都有一個與之關聯的鎖,對象能夠獲得並釋放它以便於在任意時間只有一個線程能夠訪問對象實例變量和方法。Lock語句就是經過Monitor.Enter()和Monitor.Exit()實現的。當Lock(lockInstance){ }結構開始執行時調用Monitor.Enter(lockInstance)鎖定lockInstance臨界區,當該結構執行結束,調用monitor.Exit(lockInstance)釋放lockInstance臨界區。app

原理:對於任何一個對象來講,它在內存中的第一部分放置的是全部方法的地址,第二部分放着一個索引,這個索引指向CLR中的SyncBlock Cache區域中的一個SyncBlock,當你執行Monitor.Enter(Object)時,若是object的索引值爲負數,就從SyncBlock Cache中選取一個SyncBlock將其地址放在object的索引中,這樣就完成了以object爲標誌的鎖定,其餘的線程想再次進行Monitor.Enter(object)操做,將得到object的已經爲正值的索引,而後就等待,直到索引變爲負數,即調用Monitor.Exit(object)將索引變爲負數,等待的線程開始執行。this

lock語句具備如下格式:spa

lock (x)
{
    // Your code...
}

其中x是引用類型的表達式,lock(x)徹底等同於:線程

object __lockObj = x;
bool __lockWasTaken = false;
try
{
    System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken);  //鎖定臨界區
    // Your code...
}
finally
{
    if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj);   //釋放臨界區
}

準則:當同步對共享資源的線程訪問時,請鎖定專用對象實例(例如,private readonly object balanceLock = new object();),避免對不一樣的共享資源使用相同的lock對象實例,由於這可能致使死鎖或鎖爭用。 具體而言,避免將如下對象用做lock對象:code

一、避免對不一樣的共享資源使用相同的lock對象實例,防止死鎖:對象

class locktest
{
  private readonly object commonLock = new object();   //假設ShareResourcesA,ShareResourcesA這兩個共享資源使用相同的鎖commonLock
  lock(commonLock)
  {
    //這裏先同步ShareResourcesA資源,commonLock對象被鎖定
    //do something...
    lock(commonLock)    //由於commonLock對象被鎖定,因此這裏等待釋放,永久等待形成死鎖
    {
      //這裏先同步ShareResourcesB資源,commonLock對象被鎖定
      //do something... 
    }
    //同步ShareResourcesB資源完成,commonLock對象被釋放
  }
  //同步ShareResourcesA資源完成,commonLock對象被釋放
}

二、避免使用lock(this),由於調用方可能將其用做lock致使死鎖,另外若是外部須要調用此對象則會發生阻塞不穩定的現象:blog

using System;   
using System.Threading;     
namespace Namespace1   
{   
    class C1   
    {   
        private bool deadlocked = true;   
  
        //這個方法用到了lock,咱們但願lock的代碼在同一時刻只能由一個線程訪問   
        public void LockMe(object o)   
        {   
            lock (this)   
            {   
                while(deadlocked)   
                {   
                    deadlocked = (bool)o;   
                    Console.WriteLine("Foo: I am locked :(");   
                    Thread.Sleep(500);   
                }   
            }   
        }   
  
        //全部線程均可以同時訪問的方法   
        public void DoNotLockMe()   
        {   
            Console.WriteLine("I am not locked :)");   
        }   
    }   
 
    class Program   
    {   
        static void Main(string[] args)   
        {   
            C1 c1 = new C1();   //在主線程中lock c1   
            lock(c1)   
            {   
                //調用沒有被lock的方法   
                c1.DoNotLockMe();   
                //調用被lock的方法,並試圖將deadlock解除,將出現死鎖   
                c1.LockMe(false);   
            }   
        }   
    }  
}

三、避免使用lock("string"),由於字符串被公共語言運行庫「暫留(intern pool)」。當有多個字符串變量包含了一樣的字符串實際值時,CLR可能不會爲它們重複地分配內存,而是讓它們通通指向同一個字符串對象實例。因此一旦將這個字符串實例鎖住了,那麼整個應用程序中的全部定義了相同的字符串值的實例都不能正常的執行了。索引

String s1 = "Hello";
String s2 = "Hello";                       
bool same = (object)s1 == (object)s2;   //返回true

四、避免使用lock(typeof(Class)),由於和lock("string")同樣範圍太大了,可能會致使須要正常運行的實例不可用。內存

五、lock必須鎖定引用類型且不爲null,由於若是傳入值類型會裝箱,下次代碼運行到這裏又會裝箱,這樣每次lock的都將是一個新的不一樣的對象,因此鎖不住。資源

六、讓咱們人爲用嵌套lock製造一個死鎖:

A a= new A();
B b= new B();
lock(a) 
{
  //do....
  lock (b)
  {
    //do......
  }
}
lock(b) 
{
  //do....
  lock (a)
  {
    //do......
  }
}

假設同時執行代碼2和代碼3將致使死鎖。

七、建議將lock鎖的對象設置成private static readonly object obj = new object();若是隻針對當前對象lock鎖能夠去掉static,靜態的鎖對象能夠被多個實例共用,readonly是確保對象不被修改以防止鎖失敗,應避免鎖定public類型,不然實例將超出代碼的控制範圍,防止外部類也鎖定這個obj對象,設置成private則外部類無權訪問。

相關文章
相關標籤/搜索