我想看看如果更改Monitor.Enter()独占锁定的对象的引用会发生什么。正如所料,抛出了SynchronizationLockException。但是我惊讶地看到几个线程在异常被抛出之前越过Monitor。
以下是以下代码的作用。
此时我预计会抛出某种异常,但直到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);
}
}
}
答案 0 :(得分:4)
您所看到的是预期的行为。用于将引用传递给Monitor.Enter()
的实际变量没有什么特别之处。更改引用不应阻止其他线程获取独占锁,因为该变量具有新值,并且该引用未在任何位置锁定。
你的问题是Exit
,因为调用Exit
的线程没有对传入的引用进行独占锁定。另一个线程可能对它有锁定,但线程你“执行中没有。”
正如您所知,这就是为什么始终最好使用变量进行锁定,而变量的引用永远不会改变。如果资源的变量可能会发生变化,请使用新的参考。
这是否足够清楚?
答案 1 :(得分:2)
'x'是对象的引用;它不是对象本身。当你这样做
x = Thread.CurrentThread.ManagedThreadId.ToString();
您丢弃了之前引用的x的锁定对象,并使x引用其他对象。现在当你做
Monitor.Exit(x);
你得到了异常,因为这个对象实际上并没有被锁定 - 你锁定的对象现在是垃圾收集器收集的垃圾。
答案 2 :(得分:1)
此行为的原因是您在此处更改x的值:
x = Thread.CurrentThread.ManagedThreadId.ToString();
所以当你到达 p>
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
对象说:“嘿,你觉得你在做什么?你没有锁定!吃这个例外小傻逼”