本文转载自:http://www.cnblogs.com/promise-7/articles/2354077.html

一、Lock定义

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

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

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

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

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

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

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

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

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

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关键字

原文链接:http://www.soaspx.com/dotnet/csharp/csharp_20120104_8511.html

关于lock网上说法一大堆,但是关于实际用法的实例还是比较多的,但是多而不精,没说的很透彻,但是这个例子是对多线程中使用lock关键字是一个相当好的实例。很郁闷现在网上找到像样的文章都没有了,抄来抄去!!又不注明网址,还当自己的是原创!找个例子都找不到,还不如自己来~

下面引入lock关键字的理论:

在应用程序中使用多个线程的一个好处是每个线程都可以异步执行。对于 Windows 应用程序,耗时的任务可以在后台执行,而使应用程序窗口和控件保持响应。对于服务器应用程序,多线程处理提供了用不同线程处理每个传入请求的能力。否则,在完全满足前一个请求之前,将无法处理每个新请求。 然而,线程的异步特性意味着必须协调对资源(如文件句柄、网络连接和内存)的访问。否则,两个或更多的线程可能在同一时间访问相同的资源,而每个线程都不知道其他线程的操作。结果将产生不可预知的数据损坏。 对于整数数据类型的简单操作,可以用 Interlocked 类的成员来实现线程同步。对于其他所有数据类型和非线程安全的资源,只有使用本主题中的结构才能安全地执行多线程处理。

lock 关键字可以用来确保代码块完成运行,而不会被其他线程中断。这是通过在代码块运行期间为给定对象获取互斥锁来实现的。

提供给 lock 关键字的参数必须为基于引用类型的对象,该对象用来定义锁的范围。在上例中,锁的范围限定为此函数,因为函数外不存在任何对该对象的引用。如果确实存在此类引用,锁的范围将扩展到该对象。严格地说,提供给 lock 的对象只是用来唯一地标识由多个线程共享的资源,所以它可以是任意类实例。然而,实际上,此对象通常表示需要进行线程同步的资源。例如,如果一个容器对象将被多个线程使用,则可以将该容器传递给 lock,而 lock 后面的同步代码块将访问该容器。只要其他线程在访问该容器前先锁定该容器,则对该对象的访问将是安全同步的。

通常,最好避免锁定 public 类型或锁定不受应用程序控制的对象实例。例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。锁定字符串尤其危险,因为字符串被公共语言运行库 (CLR)“暂留”。 这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。因此,最好锁定不会被暂留的私有或受保护成员。某些类提供专门用于锁定的成员。例如,Array 类型提供 SyncRoot。许多集合类型也提供 SyncRoot。

下面有注释,有一定线程基础都是可以看懂的。【VS2008 .NET3.5】

/*
该实例是一个线程中lock用法的经典实例,使得到的balance不会为负数
同时初始化十个线程,启动十个,但由于加锁,能够启动调用WithDraw方法的可能只能是其中几个
作者:http://hi.baidu.com/jiang_yy_jiang
*/
using System; namespace ThreadTest29
{
class Account
{
private Object thisLock = new object();
int balance;
Random r = new Random(); public Account(int initial)
{
balance = initial;
} int WithDraw(int amount)
{
if (balance < 0)
{
throw new Exception("负的Balance.");
}
//确保只有一个线程使用资源,一个进入临界状态,使用对象互斥锁,10个启动了的线程不能全部执行该方法
lock (thisLock)
{
if (balance >= amount)
{
Console.WriteLine("----------------------------:" + System.Threading.Thread.CurrentThread.Name + "---------------"); Console.WriteLine("调用Withdrawal之前的Balance:" + balance);
Console.WriteLine("把Amount输入 Withdrawal :-" + amount);
//如果没有加对象互斥锁,则可能10个线程都执行下面的减法,加减法所耗时间片段非常小,可能多个线程同时执行,出现负数。
balance = balance - amount;
Console.WriteLine("调用Withdrawal之后的Balance :" + balance);
return amount;
}
else
{
//最终结果
return 0;
}
}
}
public void DoTransactions()
{
for (int i = 0; i < 100; i++)
{
//生成balance的被减数amount的随机数
WithDraw(r.Next(1, 100));
}
}
} class Test
{
static void Main(string[] args)
{
//初始化10个线程
System.Threading.Thread[] threads = new System.Threading.Thread[10];
//把balance初始化设定为1000
Account acc = new Account(1000);
for (int i = 0; i < 10; i++)
{
System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(acc.DoTransactions));
threads[i] = t;
threads[i].Name = "Thread" + i.ToString();
}
for (int i = 0; i < 10; i++)
{
threads[i].Start();
}
Console.ReadKey();
}
}
}

效果如下:

c#初学-多线程中lock用法的经典实例的更多相关文章

  1. 多线程中lock用法的经典实例

    多线程中lock用法的经典实例 一.Lock定义     lock 关键字可以用来确保代码块完成运行,而不会被其他线程中断.它可以把一段代码定义为互斥段(critical section),互斥段在一 ...

  2. 一个线程中lock用法的经典实例

    /* 该实例是一个线程中lock用法的经典实例,使得到的balance不会为负数 同时初始化十个线程,启动十个,但由于加锁,能够启动调用WithDraw方法的可能只能是其中几个 作者:http://h ...

  3. c#多线程中Lock()关键字的用法小结

    本篇文章主要是对c#多线程中Lock()关键字的用法进行了详细的总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助     本文介绍C# lock关键字,C#提供了一个关键字lock,它可以把一段 ...

  4. Java多线程中Lock的使用

    Jdk1.5以后,在java.util.concurrent.locks包下,有一组实现线程同步的接口和类,说到线程的同步,可能大家都会想到synchronized关键字, 这是java内置的关键字, ...

  5. python3多线程应用详解(第四卷:图解多线程中LOCK)

    先来看下图形对比: 发现没有这种密集型计算的任务中,多线程没有穿行的速率快,原因就是多线程在线程切换间也是要耗时的而密集型计算任务执行时几乎没以偶IO阻塞,这样你说谁快

  6. C#中Lock静态字段和实例字段

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  7. python多线程threading.Lock锁用法实例

    本文实例讲述了python多线程threading.Lock锁的用法实例,分享给大家供大家参考.具体分析如下: python的锁可以独立提取出来 mutex = threading.Lock() #锁 ...

  8. python 多线程中的同步锁 Lock Rlock Semaphore Event Conditio

    摘要:在使用多线程的应用下,如何保证线程安全,以及线程之间的同步,或者访问共享变量等问题是十分棘手的问题,也是使用多线程下面临的问题,如果处理不好,会带来较严重的后果,使用python多线程中提供Lo ...

  9. java基础知识回顾之java Thread类学习(八)--java多线程通信等待唤醒机制经典应用(生产者消费者)

     *java多线程--等待唤醒机制:经典的体现"生产者和消费者模型 *对于此模型,应该明确以下几点: *1.生产者仅仅在仓库未满的时候生产,仓库满了则停止生产. *2.消费者仅仅在有产品的时 ...

随机推荐

  1. SQLServer性能优化之 nolock,大幅提升数据库查询性能

    公司数据库随着时间的增长,数据越来越多,查询速度也越来越慢.进数据库看了一下,几十万调的数据,查询起来确实很费时间. 要提升SQL的查询效能,一般来说大家会以建立索引(index)为第一考虑.其实除了 ...

  2. Hello Mybatis 02 mybatis generator

    接着上一篇文章通过Mybatis完成了一个User的CRUD的功能之后,这篇开始还需要建立一个Blog类,这样就可以模拟一个简单的微博平台的数据库了. 数据库准备 首先我们,还是需要在数据库中新建一个 ...

  3. IBatisNet:让insert操作返回新增记录的主键值

    项目引用ibatis包: IBatisNet.Common.dll --文件版本1.6.2.0 IBatisNet.DataAccess.dll IBatisNet.DataMapper.dll 项目 ...

  4. Java的默认编码

    摘要: 1.Java的默认编码内容: 1.Java的默认编码 java的src.zip包中的java.nio.charset.Charset类中defaultCharset()方法说明java的编码类 ...

  5. 3.实现一个名为Person的类和它的子类Employee,Employee有两个子类Faculty 和Staff。

    23.实现一个名为Person的类和它的子类Employee,Employee有两个子类Faculty 和Staff. 具体要求如下: (1)Person类中的属性有:姓名name(String类型) ...

  6. Atitti css3 新特性attilax总结

    Atitti css3 新特性attilax总结 图片发光效果2 透明渐变效果2 文字描边2 背景拉伸2 CSS3 选择器(Selector)4 @Font-face 特性7 Word-wrap &a ...

  7. iOS-应用上架

    其实挺讨厌苹果这一套上架机制,审核周期长,而且网速又慢,但又没办法,只能上架到AppStore,无奈. 上架前需要确保应用图标齐全,并在Info.plist中的Icon files添加了png图片,不 ...

  8. draggable属性设置元素是否可拖动。

    设置标签属性draggable="true"将一个标签内的元素拖动到另外一个标签进行显示: <!DOCTYPE HTML> <html> <head& ...

  9. 将图片的二进制字节字符串在HTML页面以图片形式输出

    具体实现代码如下: 1.新建一个一般处理程序: Image.ashx using System; using System.Collections.Generic; using System.Linq ...

  10. 快速入门系列--WCF--07传输安全、授权与审核

    这部分主要涉及企业级应用的安全问题,一般来说安全框架主要提供3个典型的安全行为:认证.授权和审核.除了典型的安全问题,对于一个以消息作为通信手段的分布式应用,还需要考虑消息保护(Message Pro ...