在MSDN文档说,
public class SomeObject { public void SomeOperation() { lock(this) { //Access instance variables } } }
如果可以公开访问实例,那就是"一个问题".我想知道为什么?是因为锁定的持有时间超过了必要的时间吗?还是有一些更阴险的原因?
this
在锁定语句中使用它是一种糟糕的形式,因为通常无法控制其他人可能锁定该对象.
为了正确规划并行操作,应特别注意考虑可能的死锁情况,并且具有未知数量的锁定入口点会妨碍这一点.例如,任何具有对象引用的人都可以在没有对象设计者/创建者知道的情况下锁定它.这增加了多线程解决方案的复杂性,并可能影响其正确性.
私有字段通常是更好的选择,因为编译器将对其强制实施访问限制,并且它将封装锁定机制.使用this
通过将部分锁定实现暴露给公众来违反封装.this
除非记录在案,否则您还不清楚是否会获得锁定.即使这样,依靠文档来防止问题也是次优的.
最后,存在一种常见的误解,即lock(this)
实际修改作为参数传递的对象,并以某种方式使其成为只读或不可访问.这是错误的.作为参数传递的对象lock
仅用作键.如果已经锁定了该锁,则无法进行锁定; 否则,允许锁定.
这就是为什么使用字符串作为lock
语句中的键是不好的,因为它们是不可变的并且在应用程序的各个部分之间共享/访问.您应该使用私有变量,Object
实例将很好地使用.
以下面的C#代码为例.
public class Person { public int Age { get; set; } public string Name { get; set; } public void LockThis() { lock (this) { System.Threading.Thread.Sleep(10000); } } } class Program { static void Main(string[] args) { var nancy = new Person {Name = "Nancy Drew", Age = 15}; var a = new Thread(nancy.LockThis); a.Start(); var b = new Thread(Timewarp); b.Start(nancy); Thread.Sleep(10); var anotherNancy = new Person { Name = "Nancy Drew", Age = 50 }; var c = new Thread(NameChange); c.Start(anotherNancy); a.Join(); Console.ReadLine(); } static void Timewarp(object subject) { var person = subject as Person; if (person == null) throw new ArgumentNullException("subject"); // A lock does not make the object read-only. lock (person.Name) { while (person.Age <= 23) { // There will be a lock on 'person' due to the LockThis method running in another thread if (Monitor.TryEnter(person, 10) == false) { Console.WriteLine("'this' person is locked!"); } else Monitor.Exit(person); person.Age++; if(person.Age == 18) { // Changing the 'person.Name' value doesn't change the lock... person.Name = "Nancy Smith"; } Console.WriteLine("{0} is {1} years old.", person.Name, person.Age); } } } static void NameChange(object subject) { var person = subject as Person; if (person == null) throw new ArgumentNullException("subject"); // You should avoid locking on strings, since they are immutable. if (Monitor.TryEnter(person.Name, 30) == false) { Console.WriteLine("Failed to obtain lock on 50 year old Nancy, because Timewarp(object) locked on string \"Nancy Drew\"."); } else Monitor.Exit(person.Name); if (Monitor.TryEnter("Nancy Drew", 30) == false) { Console.WriteLine("Failed to obtain lock using 'Nancy Drew' literal, locked by 'person.Name' since both are the same object thanks to inlining!"); } else Monitor.Exit("Nancy Drew"); if (Monitor.TryEnter(person.Name, 10000)) { string oldName = person.Name; person.Name = "Nancy Callahan"; Console.WriteLine("Name changed from '{0}' to '{1}'.", oldName, person.Name); } else Monitor.Exit(person.Name); } }
控制台输出
'this' person is locked! Nancy Drew is 16 years old. 'this' person is locked! Nancy Drew is 17 years old. Failed to obtain lock on 50 year old Nancy, because Timewarp(object) locked on string "Nancy Drew". 'this' person is locked! Nancy Smith is 18 years old. 'this' person is locked! Nancy Smith is 19 years old. 'this' person is locked! Nancy Smith is 20 years old. Failed to obtain lock using 'Nancy Drew' literal, locked by 'person.Name' since both are the same object thanks to inlining! 'this' person is locked! Nancy Smith is 21 years old. 'this' person is locked! Nancy Smith is 22 years old. 'this' person is locked! Nancy Smith is 23 years old. 'this' person is locked! Nancy Smith is 24 years old. Name changed from 'Nancy Drew' to 'Nancy Callahan'.
因为如果人们可以获得你的对象实例(即:你的this
)指针,那么他们也可以尝试锁定同一个对象.现在他们可能没有意识到你在this
内部锁定,所以这可能会导致问题(可能是死锁)
除此之外,它也是不好的做法,因为它锁定"太多"
例如,您可能有一个成员变量List
,并且您实际需要锁定的唯一内容是该成员变量.如果在函数中锁定整个对象,则会阻止调用这些函数的其他东西等待锁定.如果这些函数不需要访问成员列表,那么您将导致其他代码无缘无故地等待并放慢您的应用程序的速度.
看一下MSDN主题线程同步(C#编程指南)
通常,最好避免锁定公共类型或超出应用程序控制范围的对象实例.例如,如果可以公开访问实例,则lock(this)可能会出现问题,因为超出您控制范围的代码也可能会锁定该对象.这可能会导致两个或多个线程等待释放同一对象的死锁情况.锁定公共数据类型而不是对象可能会出于同样的原因导致问题.锁定文字字符串特别危险,因为文字字符串由公共语言运行库(CLR)实现.这意味着整个程序的任何给定字符串文字都有一个实例,完全相同的对象代表所有线程上所有正在运行的应用程序域中的文字.因此,在应用程序进程中的任何位置放置在具有相同内容的字符串上的锁会锁定应用程序中该字符串的所有实例.因此,最好锁定未实习的私人或受保护成员.有些类专门为锁定提供成员.例如,Array类型提供SyncRoot.许多集合类型也提供SyncRoot成员.
我知道这是一个老线程,但是因为人们仍然可以看到这一点并依赖它,所以指出这个问题lock(typeof(SomeObject))
要差得多lock(this)
.话说回来; 对Alan的真诚赞誉,指出这lock(typeof(SomeObject))
是不好的做法.
一个实例System.Type
是最通用的粗粒度对象之一.至少,System.Type的实例是AppDomain的全局实例,.NET可以在AppDomain中运行多个程序.这意味着如果两个完全不同的程序都试图在同一类型实例上获得同步锁定,那么即使在创建死锁的程度上,它们也可能会相互干扰.
所以lock(this)
不是特别健壮的形式,可能会引起问题,应该总是引起所有引用的眉毛.然而,广泛使用的,相对备受尊重且显然稳定的代码如log4net广泛使用锁(this)模式,即使我个人更喜欢看到模式改变.
但是lock(typeof(SomeObject))
开辟了一个全新的,增强的蠕虫病毒.
物有所值.
......以及完全相同的参数也适用于此构造:
lock(typeof(SomeObject))
想象一下,你在办公室里有一位技术娴熟的秘书,这是该部门的共享资源.有一段时间,你会冲向他们,因为你有一项任务,只是希望你的另一个同事还没有声称他们.通常你只需要等待一段时间.
由于关怀是分享,您的经理决定客户也可以直接使用秘书.但这有一个副作用:客户甚至可能在为这个客户工作时声称他们,并且您还需要他们执行部分任务.发生死锁,因为声明不再是层次结构.通过不允许客户首先声明它们,可以一起避免这种情况.
lock(this)
我们看到的很糟糕.外部对象可能会锁定对象,因为您无法控制谁在使用该类,所以任何人都可以锁定它...这是上面描述的确切示例.同样,解决方案是限制物体的暴露.但是,如果你有private
,protected
或internal
类,你可能已经控制了谁是你的锁定对象上,因为你相信你自己写的代码.所以这里的信息是:不要将其暴露为public
.此外,确保在类似方案中使用锁可以避免死锁.
与此完全相反的是锁定整个应用程序域中共享的资源 - 最糟糕的情况.这就像把你的秘书放在外面,允许每个人在那里声称他们.结果是完全混乱 - 或者在源代码方面:这是一个坏主意; 扔掉它然后重新开始.那我们该怎么做呢?
类型在应用程序域中共享,正如大多数人在此指出的那样.但是我们可以使用更好的东西:字符串.原因是汇集了字符串.换句话说:如果您有两个在应用程序域中具有相同内容的字符串,则它们可能具有完全相同的指针.由于指针用作锁定键,因此您基本上得到的是"准备未定义行为"的同义词.
同样,你不应该锁定WCF对象,HttpContext.Current,Thread.Current,Singletons(一般)等.最简单的方法是避免所有这些? private [static] object myLock = new object();