4
class Program
{
    static object test = new object();
    static void Main(string[] args)
    {
        new Program().test2();
        Console.ReadKey();
    }

    public void test1()
    {
        lock (test)
        {
            Console.WriteLine("test1");
        }
    }

    public void test2()
    {
        lock (test)
        {
            test1();
            Console.WriteLine("test2");
        }
    }
}

上面的代码是否应该首先完成 test2() 的 lock 语句中的语句,然后转到 test1()?(即输出不应该是这样的吗?:test2 test1)

4

3 回答 3

15

不。事件的顺序(标识代表调用堆栈或逻辑操作)是:

  • 主要调用 test2
    • test2 尝试获取与测试对象关联的监视器(lock语句的开头)
      • 监视器当前是无主的。成功!
      • 当前线程现在“拥有”该监视器,计数为 1
    • test2 调用 test1
      • test1 尝试获取测试对象的监视器(lock语句的开头)
        • 监视器当前拥有......但由当前线程拥有。成功!
        • 当前线程仍然“拥有”监视器,计数为 2
      • test1 打印“test1”
      • test1 释放监视器(lock语句结束)
        • 当前线程仍然“拥有”监视器,计数为 1
      • test1 返回
    • test2 打印“test2”
    • test2 释放监视器(lock语句结束)
      • 监视器现在是无主的(因此另一个线程可以获取它)
    • test2 返回

重要的是要注意监视器是可重入的——如果当前线程已经拥有监视器,那么再次尝试获取它只会增加计数,而不是阻塞。

如果监视器不是可重入的,则输出不会是“test2,test1”——它只是死锁。

于 2012-06-30T08:22:21.487 回答
11

监视器在同一线程上可重入。避免意外死锁非常重要,如果没有这种行为,您的代码将完全冻结。

Mutex 也是可重入的,而 Semaphore 则不是。

实现非常简单。监视器存储两条信息。进入它的线程的所有者 Thread.ManagedId 和一个计算它进入次数的计数器。所以第一个锁可以进入,因为它不属于它,它将所有者设置为您的线程并将计数设置为 1。第二个锁允许进入,因为线程 ID 匹配,计数增加到 2。在结束时第二次锁定,计数再次递减到 1。在第一个结束时,计数递减到 0 并重置所有者。

于 2012-06-30T08:24:24.060 回答
2

锁定不应该在单线程场景中使用。它的目的是用于对相同方法或对象实例的跨线程调用。

您注意到的行为是正常的。

当两个或多个线程可以同时访问它们时,您通常希望同步对资源(变量、集合等)的访问。

于 2012-06-30T08:22:34.253 回答