为什么将lock(this)视为不良实践?
为什么将lock(this)视为不良实践?
MSDN文档中说:
public class SomeObject { public void SomeOperation() { lock(this) { //Access instance variables } } }
“如果实例可以公开访问,则会有问题”。我想知道为什么?是因为锁定时间超过必要时间?还是有其他更加隐蔽的原因?
admin 更改状态以发布 2023年5月24日
在锁定语句中使用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'.