由目标对象自身锁定

本文关键字:锁定 目标对象 | 更新日期: 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等),只锁定其中一个仍然可以工作(如果你在任何地方都一致地锁定相同的对象),但会导致可读性降低,更难理解代码。

锁保证正常工作,即使垃圾收集器移动了你锁定的对象。