理解为什么在这个实现中会发生死锁

8

我是一个多线程的新手,我看到了这个例子:

public class TestThread {
   public static Object Lock1 = new Object();
   public static Object Lock2 = new Object();

   public static void main(String args[]) {

      ThreadDemo1 T1 = new ThreadDemo1();
      ThreadDemo2 T2 = new ThreadDemo2();
      T1.start();
      T2.start();
   }

   private static class ThreadDemo1 extends Thread {
      public void run() {
         synchronized (Lock1) {
            System.out.println("Thread 1: Holding lock 1...");
            try { Thread.sleep(10); }
            catch (InterruptedException e) {}
            System.out.println("Thread 1: Waiting for lock 2...");
            synchronized (Lock2) {
               System.out.println("Thread 1: Holding lock 1 & 2...");
            }
         }
      }
   }
   private static class ThreadDemo2 extends Thread {
      public void run() {
         synchronized (Lock2) {
            System.out.println("Thread 2: Holding lock 2...");
            try { Thread.sleep(10); }
            catch (InterruptedException e) {}
            System.out.println("Thread 2: Waiting for lock 1...");
            synchronized (Lock1) {
               System.out.println("Thread 2: Holding lock 1 & 2...");
            }
         }
      }
   } 
}

这会导致以下样例输出:
Thread 1: Holding lock 1...
Thread 2: Holding lock 2...
Thread 1: Waiting for lock 2...
Thread 2: Waiting for lock 1...

即,出现了死锁。然而,如果我们改变第二个线程中获取锁的顺序,使其现在看起来像这样:

public class TestThread {
   public static Object Lock1 = new Object();
   public static Object Lock2 = new Object();

   public static void main(String args[]) {

      ThreadDemo1 T1 = new ThreadDemo1();
      ThreadDemo2 T2 = new ThreadDemo2();
      T1.start();
      T2.start();
   }

   private static class ThreadDemo1 extends Thread {
      public void run() {
         synchronized (Lock1) {
            System.out.println("Thread 1: Holding lock 1...");
            try { Thread.sleep(10); }
            catch (InterruptedException e) {}
            System.out.println("Thread 1: Waiting for lock 2...");
            synchronized (Lock2) {
               System.out.println("Thread 1: Holding lock 1 & 2...");
            }
         }
      }
   }
   private static class ThreadDemo2 extends Thread {
      public void run() {
         synchronized (Lock1) {
            System.out.println("Thread 2: Holding lock 1...");
            try { Thread.sleep(10); }
            catch (InterruptedException e) {}
            System.out.println("Thread 2: Waiting for lock 2...");
            synchronized (Lock2) {
               System.out.println("Thread 2: Holding lock 1 & 2...");
            }
         }
      }
   } 
}

它按预期工作,示例输出如下:
Thread 1: Holding lock 1...
Thread 1: Waiting for lock 2...
Thread 1: Holding lock 1 & 2...
Thread 2: Holding lock 1...
Thread 2: Waiting for lock 2...
Thread 2: Holding lock 1 & 2...

有人能给我解释一下第一个代码中发生死锁的原因,以及第二个代码的改变为什么可以解决它吗?

3个回答

5
以下是可能发生的第一种情况:
线程1获取锁Lock1并睡眠10毫秒。现在线程2获取锁Lock2并睡眠10毫秒。
现在线程1试图获取锁Lock2,但无法获得,因为它已被线程2占用,而线程2则尝试获取由线程1锁定的Lock1。
在第二种情况下,假设选择第一个线程运行。它获取到Lock1,而另一个线程被阻塞,因为它也在尝试获取Lock1。现在线程1进入睡眠状态,并继续进行第二个(嵌套的)同步块。它尝试获取它(因为它仍然是空闲的)-现在它获得了2个锁。另一个线程仍然被阻塞。
只有在执行完成后,它才会释放两个锁(因为它退出了同步块),现在JVM可以自由决定选择哪个线程运行,但实际上并不重要,相同的逻辑会发生。

谢谢。我现在可以理解第一个情况了。但是第二种情况还是有些模糊。所以线程2正在等待第一个锁。一旦它被线程1释放,那么会发生什么? - SexyBeast
@AttitudeMonger 线程2获取它并开始执行同步块下的第一条语句。 - Maroun
哪个是 System.out.println("Thread 2: Holding lock 1 & 2...");?然后它释放了锁2,这是由线程1获取的,并打印出 System.out.println("Thread 1: Holding lock 1 & 2..."); - SexyBeast
@AttitudeMonger 没错。 - Maroun
为什么第二个线程在获取锁1后释放锁2? - SexyBeast
显示剩余2条评论

2

您所看到的是锁顺序,这是一种常见的死锁预防方法。

在您的第一个情况中,请考虑以下执行顺序,其中当前指令指针位于每个线程的标记位置:

  Thread 1:
     obtain lock 1
===>
     obtain lock 2
  Thread 2:
     obtain lock 2
===>
     obtain lock 1

现在,线程1接下来试图获取锁2,但是无法获取,因为锁2被线程2持有。线程2接下来试图获取锁1,但是无法获取,因为它被线程1持有。这是一种典型的循环资源依赖关系,因此导致死锁。
一个全局的方法来防止这种情况发生是确保所有锁都有一个“总”顺序,并且锁总是按照该总顺序获取。
证明这个方法可行非常简单:因为所有锁的依赖关系都是“向下”的总顺序,所以你不能有锁的循环依赖。

1
在您的第一段代码中,第一个线程持有Lock1,而第二个线程持有Lock2。然后它们试图获取彼此持有的锁,但由于另一个线程已经持有了这些锁,所以创建了死锁。
然而,在第二段代码中,第二个线程没有持有Lock2,并且被阻塞直到第一个线程释放第一个锁。正如您的输出所示,第一个线程将在抓取第二个锁之后释放第一个锁。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接