死锁 - 在这个例子中它是如何发生的?

问题描述 投票:22回答:4

谁能解释一下:

  1. 为什么我们陷入僵局?
  2. Gaston怎么能在Alphonse退出之前进入功能弓? (它应该从函数bowBack()返回以退出函数bow() - 或)?

这是我得到的输出 - 然后程序卡住了!

阿方斯:加斯顿向我鞠躬致敬!

加斯顿:阿尔方斯向我鞠躬致敬!

public class Deadlock {
    static class Friend {
        private final String name;
        public Friend(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
        public synchronized void bow(Friend bower) {
            System.out.format("%s: %s"
                + "  has bowed to me!%n", 
                this.name, bower.getName());
            bower.bowBack(this);
        }
        public synchronized void bowBack(Friend bower) {
            System.out.format("%s: %s"
                + " has bowed back to me!%n",
                this.name, bower.getName());
        }
    }

    public static void main(String[] args) {
        final Friend alphonse = new Friend("Alphonse");
        final Friend gaston = new Friend("Gaston");

        new Thread(new Runnable() {
            public void run() { alphonse.bow(gaston); }
        }).start();
        new Thread(new Runnable() {
            public void run() { gaston.bow(alphonse); }
        }).start();
    }
} 
java multithreading deadlock synchronized
4个回答
23
投票

synchronized块/方法与this同步,即调用块/方法的对象实例。 (对于static,“对象实例”将替换为“类实例”。)

那就是你的2个对象与自己同步,而不是常见的对象。

尝试这样的事情:

public class Deadlock {
   static class Friend {
      private final String name;
      public Friend(String name) {
         this.name = name;
      }
      public String getName() {
         return this.name;
      }
      public void bow(Friend bower) {
         synchronized (getClass()) {
            System.out.format("%s: %s  has bowed to me!%n", this.name, bower.getName());
            bower.bowBack(this);
         }
      }
      public void bowBack(Friend bower) {
         synchronized (getClass()) {
            System.out.format("%s: %s has bowed back to me!%n", this.name, bower.getName());
         }
      }
   }
   public static void main(String[] args) {
      final Friend alphonse = new Friend("Alphonse");
      final Friend gaston = new Friend("Gaston");
      new Thread(new Runnable() {
         public void run() { alphonse.bow(gaston); }
      }).start();
      new Thread(new Runnable() {
         public void run() { gaston.bow(alphonse); }
      }).start();
   }
}

9
投票

线程1:alphonse实例被锁定从alphonse.bow(gaston);打印一行,然后调用gaston.bowBack()(但gaston被锁定从线程2,因为下面调用了同步的bow()实例)

线程2:gaston实例被锁定从gaston.bow(alphonse);打印一行,然后调用alphonse.bowBack()(但由于调用同步的alphonse实例,bow()从线程1锁定)

所以他们都在等待释放,不能退出bow()方法,因此死锁


6
投票

首先,synchronized的使用是错误的。 oracle tutorial很好地说:

首先,对同一对象的两个同步方法的调用不可能进行交错。

正如另一个答案所解释的那样:示例中显示的代码不使用“公共锁”(两个不同对象上的同步方法不会影响“其他”方法调用)。

除此之外:一旦你删除那些System.out.format()调用 - 你的程序可以(最常见的)不会遇到死锁。

或者:在启动线程之前将println()放在main中 - 再次,程序不会死锁。

换句话说:打印到控制台非常耗时。因此,这会极大地影响线程的时间!这里发生的是大部分时间花在那些控制台输出操作上。请参阅here以获得甚至使用相同名称的类似问题;-)


5
投票

您的示例中会发生什么:

  1. 线程Alphonse通过输入函数bow获取对Alphonse的锁定。
  2. 线程加斯顿通过输入功能bow获取对加斯顿的锁定。
  3. 线程Alphonse请求锁定Gaston进入函数bowBack,但该锁目前由Thread Gaston持有,因此Alphonse被迫等待。
  4. 线程加斯顿请求锁定Alphonse进入函数bowBack,但该锁定目前由Thread Alphonse持有,因此Gaston被迫等待。

僵局。

为什么会这样:

synchronized函数是synchronized(this) { ... }的语法糖因此上面的类也可以这样编写:

public void bow(Friend bower) {
    synchronized (this) {
        System.out.format("%s: %s has bowed to me!%n", this.name, bower.getName());
        bower.bowBack(this);
    }
}

public void bowBack(Friend bower) {
    synchronized (this) {
        System.out.format("%s: %s has bowed back to me!%n", this.name, bower.getName());
    }
}

然而,此示例中的this是类的实例,因此每个实例都有它的单独锁。如果要在类的所有实例中锁定同一对象,则需要锁定如下的静态对象:

protected static final Object STATIC_LOCK = new Object();

public void bow(Friend bower) {
    synchronized (STATIC_LOCK) {
        System.out.format("%s: %s has bowed to me!%n", this.name, bower.getName());
        bower.bowBack(this);
    }
}

public void bowBack(Friend bower) {
    synchronized (STATIC_LOCK) {
        System.out.format("%s: %s has bowed back to me!%n", this.name, bower.getName());
    }
}

由于此LOCK对象是静态的,因此两个线程现在将锁定在同一个对象上,因此可以正确地相互锁定。请注意在这种情况下强烈建议使用关键字final,因为否则在执行期间(通过代码中的错误或疏忽)可能会更改同步锁定,这会使您回到死锁状态,原因与上述完全相同。

© www.soinside.com 2019 - 2024. All rights reserved.