一、Lock定义

    lock 关键字可以用来确保代码块完成运行,而不会被其他线程中断。它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。这是通过在代码块运行期间为给定对象获取互斥锁来实现的。

在多线程中,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生。

而在.NET中最好了解一下进程、应用域和线程的概念,因为Lock是针对线程一级的,而在.NET中应用域是否会对Lock起隔离作用,我的猜想是,即不在同一应用域中的线程无法通过Lock来中断;另外也最好能了解一下数据段、代码段、堆、栈等概念。

在C# lock关键字定义如下:

lock(expression) statement_block,其中expression代表你希望跟踪的对象,通常是对象引用。

如果你想保护一个类的实例,一般地,你可以使用this;如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了。

而statement_block就是互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。

二、简单例子

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread thread1 = new Thread(new ThreadStart(ThreadStart1));
            thread1.Name = "Thread1";
            Thread thread2 = new Thread(new ThreadStart(ThreadStart2));
            thread2.Name = "Thread2";
            Thread thread3 = new Thread(new ThreadStart(ThreadStart3));
            thread3.Name = "Thread3";

thread1.Start();
            thread2.Start();
            thread3.Start();

Console.ReadKey();
      }

static object _object = new object();
      static void Done(int millisecondsTimeout)
      {
            Console.WriteLine(string.Format("{0} -> {1}.Start", DateTime.Now.ToString("HH:mm:ss"), Thread.CurrentThread.Name));

//下边代码段同一时间只能由一个线程在执行
            lock (_object)
            {
                  Console.WriteLine(string.Format("{0} -> {1}进入锁定区域.", DateTime.Now.ToString("HH:mm:ss"), Thread.CurrentThread.Name));
                  Thread.Sleep(millisecondsTimeout);
                 Console.WriteLine(string.Format("{0} -> {1}退出锁定区域.", DateTime.Now.ToString("HH:mm:ss"), Thread.CurrentThread.Name));
            }
      }
      static void ThreadStart1()
      {
             Done(5000);
      }
      static void ThreadStart2()
      {
             Done(3000);
      }
      static void ThreadStart2()
      {
             Done(1000);
      }
   }
}

三、简单解释一下执行过程

  先来看看执行过程,代码示例如下:

private static object  ojb = new object();

lock(obj)

{

//锁定运行的代码段

}
   假设线程A先执行,线程B稍微慢一点。线程A执行到lock语句,判断obj是否已申请了互斥锁,判断依据是逐个与已存在的锁进行object.ReferenceEquals比较(此处未加证实),如果不存在,则申请一个新的互斥锁,这时线程A进入lock里面了。

  这时假设线程B启动了,而线程A还未执行完lock里面的代码。线程B执行到lock语句,检查到obj已经申请了互斥锁,于是等待;直到线程A执行完毕,释放互斥锁,线程B才能申请新的互斥锁并执行lock里面的代码。

四、Lock的对象选择问题

接下来说一些lock应该锁定什么对象。

    1、为什么不能lock值类型

比如lock(1)呢?lock本质上Monitor.Enter,Monitor.Enter会使值类型装箱,每次lock的是装箱后的对象。lock其实是类似编译器的语法糖,因此编译器直接限制住不能lock值类型。退一万步说,就算能编译器允许你lock(1),但是object.ReferenceEquals(1,1)始终返回false(因为每次装箱后都是不同对象),也就是说每次都会判断成未申请互斥锁,这样在同一时间,别的线程照样能够访问里面的代码,达不到同步的效果。同理lock((object)1)也不行。

    2、Lock字符串

那么lock("xxx")字符串呢?MSDN上的原话是:

 锁定字符串尤其危险,因为字符串被公共语言运行库 (CLR)“暂留”。 这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。

    3、MSDN推荐的Lock对象

通常,最好避免锁定 public 类型或锁定不受应用程序控制的对象实例。例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。

而且lock(this)只对当前对象有效,如果多个对象之间就达不到同步的效果。

而自定义类推荐用私有的只读静态对象,比如:

 private static readonly object obj = new object();

 为什么要设置成只读的呢?这时因为如果在lock代码段中改变obj的值,其它线程就畅通无阻了,因为互斥锁的对象变了,object.ReferenceEquals必然返回false。

4、lock(typeof(Class))

与锁定字符串一样,范围太广了。

五、特殊问题:Lock(this)等的详细解释

在以前编程中遇到lock问题总是使用lock(this)一锁了之,出问题后翻看MSDN突然发现下面几行字:通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:如果实例可以被公共访问,将出现C# lock this问题。如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现 lock(“myLock”) 问题。

    来看看C# lock this问题:如果有一个类Class1,该类有一个方法用lock(this)来实现互斥:
  1. publicvoidMethod2()
  2. {
  3. lock(this)
  4. {
  5. System.Windows.Forms.MessageBox.Show("Method2End");
  6. }
  7. }

如果在同一个Class1的实例中,该Method2能够互斥的执行。但是如果是2个Class1的实例分别来执行Method2,是没有互斥效果的。因为这里的lock,只是对当前的实例对象进行了加锁。

Lock(typeof(MyType))锁定住的对象范围更为广泛,由于一个类的所有实例都只有一个类型对象(该对象是typeof的返回结果),锁定它,就锁定了该对象的所有实例,微软现在建议,不要使用lock(typeof(MyType)),因为锁定类型对象是个很缓慢的过程,并且类中的其他线程、甚至在同一个应用程序域中运行的其他程序都可以访问该类型对象,因此,它们就有可能代替您锁定类型对象,完全阻止您的执行,从而导致你自己的代码的挂起。

锁住一个字符串更为神奇,只要字符串内容相同,就能引起程序挂起。原因是在.NET中,字符串会被暂时存放,如果两个变量的字符串内容相同的话,.NET会把暂存的字符串对象分配给该变量。所以如果有两个地方都在使用lock(“my lock”)的话,它们实际锁住的是同一个对象。到此,微软给出了个lock的建议用法:锁定一个私有的static 成员变量。

.NET在一些集合类中(比如ArrayList,HashTable,Queue,Stack)已经提供了一个供lock使用的对象SyncRoot,用Reflector工具查看了SyncRoot属性的代码,在Array中,该属性只有一句话:return this,这样和lock array的当前实例是一样的。ArrayList中的SyncRoot有所不同

  1. get
  2. {
  3. if(this._syncRoot==null)
  4. {
  5. Interlocked.CompareExchange(refthis._syncRoot,newobject(),null);
  6. }
  7. returnthis._syncRoot;

其中Interlocked类是专门为多个线程共享的变量提供原子操作(如果你想锁定的对象是基本数据类型,那么请使用这个类),CompareExchange方法将当前syncRoot和null做比较,如果相等,就替换成new object(),这样做是为了保证多个线程在使用syncRoot时是线程安全的。集合类中还有一个方法是和同步相关的:Synchronized,该方法返回一个对应的集合类的wrapper类,该类是线程安全的,因为他的大部分方法都用lock来进行了同步处理,比如Add方法:

  1. publicoverridevoidAdd(objectkey,objectvalue)
  2. {
  3. lock(this._table.SyncRoot)
  4. {
  5. this._table.Add(key,value);
  6. }
  7. }

这里要特别注意的是MSDN提到:从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。即使一个集合已进行同步,其他线程仍可以修改该集合,这将导致枚举数引发异常。若要在枚举过程中保证线程安全,可以在整个枚举过程中锁定集合:

  1. QueuemyCollection=newQueue();
  2. lock(myCollection.SyncRoot){
  3. foreach(ObjectiteminmyCollection){
  4. //Insertyourcodehere.
  5. }
  6. }

最后

注意:应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:
    1)如果实例可以被公共访问,将出现 lock (this) 问题;
    2)如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题;
    3)由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现 lock("myLock") 问题;
    最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。

六、参考资料关键字

1)描述C#多线程中Lock关键字

2)解决C# lock this问题

3)基于C#中的lock关键字的总结

    4)C# lock关键字

【转】C# lock的使用的更多相关文章

  1. C#各种同步方法 lock, Monitor,Mutex, Semaphore, Interlocked, ReaderWriterLock,AutoResetEvent, ManualResetEvent

    看下组织结构: System.Object System.MarshalByRefObject System.Threading.WaitHandle System.Threading.Mutex S ...

  2. 多线程同步工具——Lock

    本文原创,转载请注明出处. 参考文章: <"JUC锁"03之 公平锁(一)> <"JUC锁"03之 公平锁(二)> 锁分独占锁与共享锁, ...

  3. java 线程 Lock 锁使用Condition实现线程的等待(await)与通知(signal)

    一.Condition 类 在前面我们学习与synchronized锁配合的线程等待(Object.wait)与线程通知(Object.notify),那么对于JDK1.5 的 java.util.c ...

  4. InnoDB:Lock & Transaction

    InnoDB 是一个支持事务的Engine,要保证事务ACID,必然会用到Lock.就像在Java编程一下,要保证数据的线程安全性,必然会用到Lock.了解Lock,Transaction可以帮助sq ...

  5. 使用四元数解决万向节锁(Gimbal Lock)问题

    问题 使用四元数可以解决万向节锁的问题,但是我在实际使用中出现问题:我设计了一个程序,显示一个三维物体,用户可以输入绕zyx三个轴进行旋转的指令,物体进行相应的转动. 由于用户输入的是绕三个轴旋转的角 ...

  6. 万向节锁(Gimbal Lock)的理解

    [TOC] 结论 我直接抛出结论: Gimbal Lock 产生的原因不是欧拉角也不是旋转顺序,而是我們的思维方式和程序的执行逻辑没有对应,也就是说是我们的观念导致这个情况的发生. 他人解释 首先我们 ...

  7. 在多线程编程中lock(string){...}隐藏的机关

    常见误用场景:在订单支付环节中,为了防止用户不小心多次点击支付按钮而导致的订单重复支付问题,我们用 lock(订单号) 来保证对该订单的操作同时只允许一个线程执行. 这样的想法很好,至少比 lock( ...

  8. 谈谈 Lock

    上来先看MSDN关于lock的叙述: lock  关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁.  下面的示例包含一个 lock 语句. lock  关键字可确保当一 ...

  9. LOCK TABLES和UNLOCK TABLES与Transactions的交互

    LOCK TABLES对事务不安全,并且在试图锁定表之前隐式提交任何活动事务. UNLOCK TABLES只有在LOCK TABLES已经获取到表锁时,会隐式提交任何活动事务.对于下面的一组语句,UN ...

  10. SQL 性能调优中可参考的几类Lock Wait

    在我们的系统出现性能问题时,往往避不开调查各种类型 Lock Wait,如Row Lock Wait.Page Lock Wait.Page IO Latch Wait等.从中找出可能的异常等待,为性 ...

随机推荐

  1. 【转】Hadoop FS Shell命令

    FS Shell 调用文件系统(FS)Shell命令应使用 bin/hadoop fs <args> 的形式. 所有的的FS shell命令使用URI路径作为参数.URI格式是scheme ...

  2. WPF中,Combox的SelectedItem属性绑定成功后,未能默认显示上一次选择的结果。

    问题描述: Combox中,设定了绑定对象,但是在第一次进入时却没有显示上次选中的项.      1)查看SelectedItem对应绑定的值,也是有的(启动时,读取上次设置的结果,来初始化界面). ...

  3. Java多线程-并发容器

    Java多线程-并发容器 在Java1.5之后,通过几个并发容器类来改进同步容器类,同步容器类是通过将容器的状态串行访问,从而实现它们的线程安全的,这样做会消弱了并发性,当多个线程并发的竞争容器锁的时 ...

  4. 把Tomcat注册为windows服务

    配置环境变量 JAVA_HOME=D:\java CLASSPATH=.;%JAVA_HOME%\lib; PATH=%JAVA_HOME%\bin; 提示:一般jre默认在jdk目录下%JAVA_H ...

  5. [有意思]The IT workers of Star Wars -- That's not a bug. It's a feature

    Yeah, that Artoo is kinda mouthy... ... now select, "restore to factory settings." That'll ...

  6. Redis学习资源

    1 redis官方网站 http://redis.io/ 2 redis中文 http://redisdoc.com/ 3 redis的设计与实现 http://www.redisbook.com/ ...

  7. WPF系列 Style

        参考 WPF: Customize your Application with Styles and Control Templates (Part 2 of 2)

  8. 微信公共号的PHP开发(基础篇)——玩一下

    最近没事儿开了个微信号,写点东西给家人啊什么的看,你们想看的话就这个嘛: 然后就意外的看到imooc上的微信公众号开发相关了.每天科研的累累的,做点这个不是很累的东西吧. 微信公共号开发 一.基础 1 ...

  9. 连载《一个程序猿的生命周期》- 44.感谢,我从事了IT相关的工作

    感谢博客园一直以来的支持,写连载都是在这里首发,相比较CSDN和开源中国气氛要好的多. 节前,想以此篇文章结束<一个程序猿的生命周期>的<生存>篇,对过10的年做一个了断,准备 ...

  10. UrlEncode 和 HtmlEncode

    UrlEncode 是将指定的字符串按URL编码规则,包括转义字符进行编码.