锁内锁的原理是什么?

5
我正在审查一本书中的示例代码,遇到了以下简化版代码。在代码中,当调用Subscribe(T subscriber)时,线程进入锁定部分。然后,在锁内调用AddToSubscribers(T subscriber)方法时,该方法有另一个锁。为什么需要这第二个锁呢?
public abstract class SubscriptionManager<T> where T : class 
{
   private static List<T> subscribers;
   private static void AddToSubscribers(T subscriber)
   {
      lock (typeof(SubscriptionManager<T>))
      {
         if (subscribers.Contains(subscriber))
            return;
         subscribers.Add(subscriber);
      }
   }

   public void Subscribe(T subscriber)
   {
      lock (typeof(SubscriptionManager<T>))
      {
         AddToSubscribers(subscriber);
      }
   }
}
4个回答

9
在这种情况下,这并不是必需的;但是,由于锁是可重入的,这可以确保任何调用者观察到AddToSubscribers的锁。实际上,出于这个原因,我会说“从Subscribe中删除它,让AddToSubscribers进行锁定”。

然而!在Type上加锁非常危险。使用字段会更安全:

// assuming static is correct
private static readonly object syncLock = new object();

并且使用lock(syncLock)。根据何时分配subscribers,您也可以只使用lock(subscribers)(而不需要额外的字段)。

我还应该指出,将实例方法添加到静态状态非常不寻常;在我看来,Subscribe应该是一个static方法,因为它与当前实例无关。


实际上,这个例子来自一本WCF书籍,SubscriptionManager<T>类是WCF服务类的基类,因此需要实例方法。我也从MSDN上读到锁定类型是危险的,正如你所说,那么上下文是WCF会有什么不同吗? - Yeonho
@Marc "private static readonly object syncLock = new object(); " - 这为所有SubscriptionManager<T>提供了一个单一的全局锁;而我认为原始代码对于每个构造类型都有一个锁(即每个不同的“T”都有一个锁)。 - Joe
5
@Joe,不是这样的。泛型类型中的静态字段是按照每个泛型类型参数排列方式来分别存储的,所以在这种情况下是按照每个 T 来存储的。 - Marc Gravell
2
@Marc 你是对的,我错了,今天学到了东西,+1 - Joe

3
在您发布的代码中,这并不是必需的。但是,您发布的代码是不完整的 - 例如,订阅者列表从未初始化。
锁定 typeof(SubscriptionManager) 可能也不是一个好主意 - 锁定 subscribers 字段会更好 - 但需要初始化 subscribers 字段,例如:
private static List<T> subscribers = new List<T>();

1

你可能应该阅读一下那个示例,并看看书上讲了什么。

对于这种情况,不需要第二个锁。

注意:该示例很危险,因为它在公共对象(类型)上加锁。通常,人们会在特殊的私有对象上加锁,以便外部代码无法通过错误地锁定相同的对象而引入死锁。


1

我也曾经遇到过需要使用嵌套锁的情况。

我的情况是,第二个锁的函数可能会从其他地方调用,因为它是一个静态函数。但是对于你的情况来说,这并不是必要的,因为每个数据成员都属于一个实例而不是静态的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接