1

我想看看如果你改变一个被 Monitor.Enter() 锁定的对象的引用会发生什么。正如预期的那样,抛出了 SynchronizationLockException。但是我很惊讶地看到在抛出异常之前有几个线程通过了监视器。

这是下面的代码正在做的事情。

  1. 创建并启动 100 个线程
  2. 让所有线程等待,直到设置 ManualResetEvent。
  3. 设置 ManualResetEvent - 有点像在 Indy 比赛中挥舞绿旗。
  4. 在 x 上设置排他锁 (Monitor.Enter(x))
  5. 更改 x 的参考。

在这一点上,我预计会引发某种异常,但直到 Monitor.Exit(x) 才会发生这种情况。真正奇怪的是,在引发异常之前,似乎有 10 - 20 个线程能够通过锁。这是怎么发生的?似乎不应该。当然,更改独占锁定对象的引用是不行的。我永远不会在真正的代码中这样做,但我仍然惊讶地看到其他线程通过监视器。你的意见?

using System;
using System.Threading;

namespace ThreadingPlayground
{
  class Program
  {
    private int _value;
    object x = new object();

    ManualResetEvent _event = new ManualResetEvent(false);

    static void Main()
    {
      Program p = new Program();
      p.StartThreads();
      Console.ReadLine();
    }

    private void StartThreads()
    {

      for(int i = 0;i<100;i++)
      {
        Thread t = new Thread(IncrementValue);
        t.Start();
      }
      _event.Set();
    }

    private void IncrementValue()
    {
      WaitHandle.WaitAll(new WaitHandle[] {_event});

      Monitor.Enter(x);

      // Change the reference of the exclusively locked object. This 
      // allows other threads to enter, should this be possible?
      x = Thread.CurrentThread.ManagedThreadId.ToString();

      Console.WriteLine(++_value);

      // throws a SynchronizationLockException 
      // but not until 10 - 20 more lines are written
      Monitor.Exit(x);
    }
  }
}

控制台输出,看起来有些线程通过了监视器??

4

3 回答 3

4

你看到的是预期的行为。用于将引用传递给Monitor.Enter(). 更改引用不应阻止其他线程获取排他锁,因为变量具有新值,并且该引用不会在任何地方锁定。

您的问题来自Exit,因为调用的线程Exit对传入的引用没有独占锁。另一个线程很可能对其有锁,但您正在执行的线程没有。

如您所知,这就是为什么最好使用其引用永远不会改变的变量进行锁定。如果您的资源变量可能发生变化,请使用新的引用。

这足够清楚吗?

于 2009-05-06T04:30:00.110 回答
2

'x' 是对对象的引用;它不是对象本身。当你这样做

      x = Thread.CurrentThread.ManagedThreadId.ToString();

您已经丢弃了 x 之前引用的锁定对象,并使 x 引用了其他对象。现在当你做

      Monitor.Exit(x);

你得到了异常,因为这个对象实际上没有被锁定 - 你锁定的对象现在是垃圾收集器收集的垃圾。

于 2009-05-06T04:33:44.530 回答
1

这种行为的原因是您在此处更改 x 的值:

x = Thread.CurrentThread.ManagedThreadId.ToString();

所以当你到达

Monitor.Exit(x)

您正在使用不同的对象释放锁。就好像你用一把钥匙放了一把挂锁,然后试着用另一把挂锁的钥匙取下挂锁。

此外,与其他指令相比,Console.Writeline 指令成本高昂,因此在其中一个线程接近终点线之前,有几个线程会进入 Monitor。

这是一个示例运行:

你启动了一堆线程。

  • 线程T1使用 object 获取锁x
  • T2尝试使用 object 获取锁x。这个线程不走运,因为我们知道它会永远等待。
  • T1变化x。它现在是一个新对象。我会打电话x'1的。
  • T3尝试获取锁。但变量x实际上引用了对象x'1。没有人锁定x'1,所以他通过了。
  • T3变化x。它现在是一个名为 的新对象x'3
  • T1写入控制台。
  • T3写入控制台。
  • T1尝试使用x指向对象的变量释放锁x'3......
  • 对象说: “Monitor嘿,你以为你在做什么?你没有那个锁!吃这个例外的小吸盘”
于 2009-05-12T15:57:10.157 回答