一、Lock定义
而statement_block就是互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。
二、简单解释一下执行过程
先来看看执行过程,代码示例如下:
这时假设线程B启动了,而线程A还未执行完lock里面的代码。线程B执行到lock语句,检查到obj已经申请了互斥锁,于是等待;直到线程A执行完毕,释放互斥锁,线程B才能申请新的互斥锁并执行lock里面的代码。
三、Lock的对象选择问题
锁定字符串尤其危险,因为字符串被公共语言运行库 (CLR)“暂留”。 这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。
private static readonly object obj = new object();
为什么要设置成只读的呢?这时因为如果在lock代码段中改变obj的值,其它线程就畅通无阻了,因为互斥锁的对象变了,object.ReferenceEquals必然返回false。
4、lock(typeof(Class))
五、特殊问题:Lock(this)等的详细解释
- publicvoidMethod2()
- {
- lock(this)
- {
- System.Windows.Forms.MessageBox.Show("Method2End");
- }
- }
如果在同一个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有所不同
- get
- {
- if(this._syncRoot==null)
- {
- Interlocked.CompareExchange(refthis._syncRoot,newobject(),null);
- }
- returnthis._syncRoot;
其中Interlocked类是专门为多个线程共享的变量提供原子操作(如果你想锁定的对象是基本数据类型,那么请使用这个类),CompareExchange方法将当前syncRoot和null做比较,如果相等,就替换成new object(),这样做是为了保证多个线程在使用syncRoot时是线程安全的。集合类中还有一个方法是和同步相关的:Synchronized,该方法返回一个对应的集合类的wrapper类,该类是线程安全的,因为他的大部分方法都用lock来进行了同步处理,比如Add方法:
- publicoverridevoidAdd(objectkey,objectvalue)
- {
- lock(this._table.SyncRoot)
- {
- this._table.Add(key,value);
- }
- }
这里要特别注意的是MSDN提到:从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。即使一个集合已进行同步,其他线程仍可以修改该集合,这将导致枚举数引发异常。若要在枚举过程中保证线程安全,可以在整个枚举过程中锁定集合:
- QueuemyCollection=newQueue();
- lock(myCollection.SyncRoot){
- foreach(ObjectiteminmyCollection
){ - //Insertyourcodehere.
- }
- }
最后
六、参考资料
原文链接:http://www.soaspx.com/dotnet/csharp/csharp_20120104_8511.html
关于lock网上说法一大堆,但是关于实际用法的实例还是比较多的,但是多而不精,没说的很透彻,但是这个例子是对多线程中使用lock关键字是一个相当好的实例。很郁闷现在网上找到像样的文章都没有了,抄来抄去!!又不注明网址,还当自己的是原创!找个例子都找不到,还不如自己来~
下面引入lock关键字的理论:
在应用程序中使用多个线程的一个好处是每个线程都可以异步执行。对于 Windows 应用程序,耗时的任务可以在后台执行,而使应用程序窗口和控件保持响应。对于服务器应用程序,多线程处理提供了用不同线程处理每个传入请求的能力。否则,在完全满足前一个请求之前,将无法处理每个新请求。 然而,线程的异步特性意味着必须协调对资源(如文件句柄、网络连接和内存)的访问。否则,两个或更多的线程可能在同一时间访问相同的资源,而每个线程都不知道其他线程的操作。结果将产生不可预知的数据损坏。 对于整数数据类型的简单操作,可以用 Interlocked 类的成员来实现线程同步。对于其他所有数据类型和非线程安全的资源,只有使用本主题中的结构才能安全地执行多线程处理。
通常,最好避免锁定 public 类型或锁定不受应用程序控制的对象实例。例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。锁定字符串尤其危险,因为字符串被公共语言运行库 (CLR)“暂留”。 这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。因此,最好锁定不会被暂留的私有或受保护成员。某些类提供专门用于锁定的成员。例如,Array 类型提供 SyncRoot。许多集合类型也提供 SyncRoot。
下面有注释,有一定线程基础都是可以看懂的。【VS2008 .NET3.5】
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();
}
}
}