了解为什么在此实现中发生死锁

时间:2016-04-25 06:47:30

标签: java multithreading concurrency deadlock

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

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 个答案:

答案 0 :(得分:5)

这是第一种情况的可能情景:

线程1获取Lock1并进入休眠状态10毫秒。现在线程2获取Lock2并进入睡眠状态10毫秒。

现在,线程1尝试获取Lock2,但是因为它被线程2获取而无法获取它,而线程2尝试获取被线程锁定的Lock1 1。

在第二种情况下,让我们假设第一个线程被选中运行。它得到Lock1,而另一个帖子被阻止,因为它也试图获得Lock1。现在,线程1进入休眠状态,然后进入第二个(嵌套的)同步块。它试图得到它(因为它仍然是免费的) - 现在他得到了 2 锁。另一个线程仍然被阻止。

只有在完成执行后,它才会释放两个锁(因为它退出synchronized块),现在JVM可以自由决定选择哪个线程,但它并不是真的重要的是,同样的逻辑发生了。

答案 1 :(得分:2)

您在此处看到的是锁定顺序,这是防止死锁的常用方法。

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

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

现在,线程1尝试接下来获取锁定2,但是不能因为线程2保持锁定2.并且线程2尝试接下来获得锁定1,但是不能,因为它由线程1保持。这是经典的循环资源依赖,从而导致死锁。

防止出现这种情况的全局方法是确保所有锁具有顺序,并且始终以该总顺序获取锁。

证明这项工作是微不足道的:因为所有锁定依赖关系在整个订单中都是“向下”,所以你不能有锁定周期。

答案 2 :(得分:1)

在你的第一段代码中,第一个主题是Lock1,第二个主要是Lock2。然后他们试图抓住彼此的锁,但是它们失败了,因为其他锁已经被另一个线程保持,因此会产生死锁。

然而,在第二段代码中,第二个线程没有保持Lock2并被阻止,直到第一个线程释放第一个锁。第一个线程将在它抓住第二个线程之后释放第一个锁定,如输出所示。