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则外部类无权访问。

相关文章
相关标签/搜索