由目标对象自身锁定
本文关键字:锁定 目标对象 | 更新日期: 2023-09-27 18:18:26
我很好奇下面两个例子的区别。
用只读对象锁定
private readonly object key = new object();
private List<int> list = new List<int>;
private void foo()
{
lock(key){
list.add(1);
}
}
case2)由目标对象本身锁定
private List<int> list = new List<int>;
private void foo()
{
lock(list){
list.add(1);
}
}
是否都是线程安全的?我想知道垃圾收集器是否在某个时候改变list
变量的地址(如0x382743 => 0x576382),以便它可能失败线程安全。
只要List<T>
代码中没有任何lock(this)
语句,这两个函数的行为将相同。
然而,因为你不总是知道一个对象是否锁定了它自己而不查看它的源代码,所以只锁定一个单独的对象更安全。
值得注意的是,从ICollection
继承的类有一个SyncRoot
属性,如果您想在不使用单独对象的情况下对集合进行锁定,则该属性显式地是您应该锁定的对象。
private List<int> list = new List<int>;
private void foo()
{
lock(((ICollection)list).SyncRoot){
list.add(1);
}
}
这在内部只是做同样的事情,你做了一个单独的new Object()
来锁定。
在两种情况下,foo()都是线程安全的。但是锁在单独的只读对象(情况1)上是首选的,因为
- 你不必担心list是否为空(锁将失败)
- 你不必担心list是否被重新赋值(将
List<T>
的新实例赋值给list可能会导致一些麻烦,例如失去锁定的代码块的原子性) - 你不必担心你是否传递列表给第三方代码(例如,作为一些函数的结果),因为它是一个很好的做法,只锁定在你独占控制下的对象(它可能导致死锁,如果另一块可能会锁定在这个对象上)。
- 当锁一次保护更多对象时(例如list1, list2等),只锁定其中一个仍然可以工作(如果你在任何地方都一致地锁定相同的对象),但会导致可读性降低,更难理解代码。
锁保证正常工作,即使垃圾收集器移动了你锁定的对象。