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

下面是下面的代码所做的事情。

  1. 创建并启动 100 个线程
  2. 让所有线程等待,直到设置 ManualResetEvent。
  3. 设置 ManualResetEvent - 有点像在印地比赛中挥舞绿旗。
  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);
    }
  }
}

Console Output, looks like some threads got past the monitor??

有帮助吗?

解决方案

您所看到的是预期的行为。用于将引用传递给 Monitor.Enter()的实际变量没有什么特别之处。更改引用不应阻止其他线程获取独占锁,因为该变量具有新值,并且该引用未在任何位置锁定。

您的问题是 Exit ,因为调用 Exit 的线程没有对传入的引用进行独占锁定。另一个线程可能已经锁定它,但你执行的线程没有。

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

这是否足够清楚?

其他提示

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

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

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

      Monitor.Exit(x);

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

出现此行为的原因是您在此处更改 x 的值:

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

所以当你到达

Monitor.Exit(x)

您正在使用不同的对象释放锁。这就好像你用一把钥匙放了一把挂锁,然后尝试用另一把挂锁上的钥匙打开挂锁。

此外,与其他指令相比,Console.Writeline 是一条成本较高的指令,因此在其中一个线程接近终点线之前,有多个线程需要进入监视器。

这是一个运行示例:

你启动了一堆线程。

  • 线 T1 获取对象的锁 x.
  • T2 尝试获取对象的锁 x. 。这个线程很不幸,因为我们知道它将永远等待。
  • T1 变化 x. 。现在它是一个新对象。我会称之为 x'1.
  • T3 尝试获取锁。但变量 x 实际上引用了该对象 x'1. 。没有人锁定过 x'1, ,所以他通过了。
  • T3 变化 x. 。现在它是一个新对象,名为 x'3.
  • T1 写入控制台。
  • T3 写入控制台。
  • T1 尝试释放变量的锁 x, ,它指向对象... x'3.
  • Monitor 对象说:“嘿,你以为你在做什么?你没有那把锁!吃这个 例外 小傻瓜”
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top