我有一些代码,我一直在学习系统,我遇到一些代码对我来说是一个代码气味,我不认为它会工作,但它确实。
我们有两个对象,对象A
和对象B
。对象A
包含一个锁对象:
private object lockObj = new object();
对象B
将获取对象A.lockObj
上的锁,当B
拥有锁时,它调用
A.SomeMethod();
A.SomeMethod()
获取
this.lockObj
并在代码中显示:
ThreadTestOne:
public class ThreadTestOne
{
public object lockObject = new object();
private List<string> lst;
private ThreadTestTwo two;
public List<string> Lst
{
get
{
return this.lst;
}
set
{
this.lst = value;
}
}
public void Run()
{
lst = new List<string>();
two = new ThreadTestTwo();
two.Run(this);
}
public void End()
{
Console.WriteLine("ThreadTestOne.End");
two.End();
}
public void LockMe()
{
Console.WriteLine("ThreadTestOne.LockMe");
lock (this.lockObject)
lst.Add("something");
Thread.Sleep(500);
}
}
ThreadTestTwo:
public class ThreadTestTwo
{
private ThreadTestOne one;
private Thread myThread;
private bool ending = false;
public void Run(ThreadTestOne a)
{
one = a;
myThread = new Thread(new ThreadStart(Consume));
Console.WriteLine("ThreadTestTwo Starting thread");
myThread.Start();
}
public void End()
{
Console.WriteLine("ThreadTestTwo.End");
ending = true;
myThread.Join();
}
public void Consume()
{
while (!ending)
{
Console.WriteLine("ThreadTestTwo one.lockObject");
lock (one.lockObject)
{
Console.WriteLine("two.LockMe");
one.LockMe();
one.Lst.Add("two");
Thread.Sleep(500);
}
}
}
}
当我查看上面的代码时,我认为one.LockMe()
应该永远无法获得lockObj
上的锁,因为ThreadTestTwo
已经有锁了。
我认为这会导致死锁。但是,当我运行上面的示例代码时,它可以工作。此外,我正在审查的代码也可以工作,并且目前正在生产中。
这不会导致抛出异常的事实让我感到困惑。我假设这是个错误,对吗?
在我测试的代码中,最初只在两次尝试获取锁后读取数据,因此我认为编译器正在删除锁。
然而,我看了看MSIL
,看到锁仍然在那里。
我的下一个想法是框架只是没有获取锁,因为我们只是在读取数据。
我在锁中添加了一个写操作,它仍然工作。然而,我可能并不完全理解锁是如何工作的。
尽管这是有效的,但我觉得这是错误的,我不能完全相信这不会在生产中引起问题。
我确实发现了这个问题:
在两个不同的代码块上使用相同的锁对象?
这是类似的,但我相信我的问题略有不同,我问的是锁定对象时,调用方法已经有一个锁在同一个对象上。
显然的代码,我有一个关于工作的问题,我想知道如何?
我假设这是错误的是错误的吗?
我注意到上面的代码有几个问题。
- 公共字段-我知道这是错误的,但这就是代码中的情况。
- 循环引用-我知道循环引用,知道为什么它不好。
感谢您提供的任何见解。
您似乎有这样的印象:类拥有一个锁(即监视器)。但事实并非如此——一个线程拥有一个监视器。
. net中的监视器是可重入的——如果一个线程已经拥有监视器,它可以再次获得它。这将增加它的"锁计数"——当线程第一次释放监视器时,它只会减少锁计数,但由于该计数仍然是正的,因此没有其他线程能够获得监视器,直到原始线程再次释放它。从Monitor.Enter
(lock
关键字调用的方法-它实际上调用TryEnter
,但是…):
同一个线程多次调用
Enter
而不被阻塞是合法的;但是,在等待该对象的其他线程解除阻塞之前,必须调用相同数量的Exit
调用。