17

首先,这是一个示例

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();
    }
}

我不明白阻塞是如何发生的。主函数启动两个线程,每个线程都开始自己的弓。

“同步”到底是什么块?为同一个对象运行相同的函数(正如我最初认为的那样)?同一类的所有对象都具有相同的功能?同一个对象的所有同步函数?同一类的所有对象的所有同步函数?

帮帮我。

4

4 回答 4

29

在 Java 中,每个都Object为线程提供了synchronize锁定或锁定它的能力。当一个方法被同步时,该方法使用它的对象实例作为锁。在您的示例中,方法bowbowBack都是synchronized,并且两者都在同一个 classFriend中。这意味着任何执行这些方法的线程都将在一个Friend实例上同步作为它的锁。

导致死锁的一系列事件是:

  1. 第一个线程开始调用alphonse.bow(gaston),它synchronizedalphonse Friend对象上。这意味着线程必须从这个对象获取锁。
  2. 第二个线程开始调用gaston.bow(alphonse),它synchronizedgaston Friend对象上。这意味着线程必须从这个对象获取锁。
  3. 现在开始的第一个线程调用bowback并等待锁定gaston被释放。
  4. 现在启动的第二个线程调用bowback并等待alphonse释放锁。

要更详细地显示事件的顺序:

  1. main()开始在主 Therad 中执行(称为线程 #1),创建两个Friend实例。到目前为止,一切都很好。
  2. 主线程使用代码启动它的第一个新线程(称为线程#2)new Thread(new Runnable() { ...。线程 #2 调用alphonse.bow(gaston),它synchronizedalphonse Friend对象上。线程#2 因此获取alphonse对象的“锁”并进入bow方法。
  3. 这里发生了一个时间片,原始线程有机会进行更多处理。
  4. 主线程启动第二个新线程(称为线程#3),就像第一个线程一样。线程 #3 调用gaston.bow(alphonse),在gaston Friend对象上同步。由于还没有人获得gaston对象实例的“锁”,线程#3成功地获得了这个锁并进入了bow方法。
  5. 这里发生了一个时间片,线程#2 有机会进行更多处理。
  6. 线程 #2现在调用bower.bowBack(this);. 这是调用的逻辑等价物。因此,此方法在实例上。此对象的锁已被获取并由另一个线程(线程#3)持有。因此,线程#2 必须等待锁定被释放。线程进入等待状态,允许线程#3 继续执行。bowergastongaston.bowBack(alphonse)synchronizedgastongaston
  7. 线程#3 现在调用bowback,在本例中,它在逻辑上与调用相同alphonse.bowBack(gaston)。为此,它需要为alphonse实例获取锁,但该锁由 Thread #2 持有。该线程现在进入等待状态。

而且您现在处于两个线程都无法执行的位置。线程#2 和线程#3 都在等待释放锁。但是如果没有线程取得进展,就不能释放任何锁。但是如果不释放锁,两个线程都无法取得进展。

因此:死锁!

死锁通常取决于发生的特定事件序列,这可能会使调试变得困难,因为它们可能难以重现。

于 2009-04-14T22:57:12.800 回答
2

同步有两个效果

  • 首先,同一对象上的同步方法的两次调用不可能交错。当一个线程正在为一个对象执行同步方法时,所有其他为同一对象调用同步方法的线程都会阻塞(暂停执行),直到第一个线程处理完该对象。
  • 其次,当同步方法退出时,它会自动与任何后续对同一对象的同步方法调用建立起之前的关系。这保证了对象状态的更改对所有线程都是可见的。

所以简而言之,它会阻止对同一对象的同步方法的任何调用。

于 2009-04-14T22:53:27.393 回答
2

同一对象的所有同步函数。将方法标记为“已同步”与在方法的全部内容周围放置“已同步 (this) {”块非常相似。我不说“相同”的原因是因为我不知道编译器是否发出相同的字节码,但是 AFAIK 定义的运行时效果是相同的。

死锁是一种经典的锁反转。一个线程锁定 alphonse。然后(或同时在多核系统上)另一个线程锁定gaston。这部分要求线程的调度恰好在正确的点交错。

然后每个线程(以任何顺序或同时)尝试获取已由另一个线程持有的锁,因此每个线程都进入睡眠状态。在对方释放其锁之前,两者都不会唤醒,但在唤醒(或终止)之前,两者都不会释放其锁。

于 2009-04-14T22:54:58.407 回答
2

同步方法与将所有这些方法代码封装到一个

synchronized(this) {
  /// code here ...
}

堵塞。

对于给定的对象实例o,一次只有一个线程可以运行任何synchronized(o)块。其他所有尝试执行此操作的线程都会哭泣,直到运行该块的线程(具有同步锁)退出该块(放弃锁)。

在您的情况下,当 Alphonse 在线程 1 中开始鞠躬时发生死锁,从而进入同步块。然后线程 1 被系统换出,因此线程 2 可以启动,并让 Gaston 鞠躬。但是 Gaston 还不能退缩,因为它正在 Alphonse 上进行同步,并且 Thread 1 已经拥有那个锁。因此它将等待线程 1 离开该块。然后系统将换回线程 1,这将尝试让 Alphonse 回弓。除非它不能这样做,因为线程 2 在 Gaston 上具有同步锁。两个线程现在都卡住了,等待对方完成鞠躬后才能鞠躬……

于 2009-04-14T23:03:55.730 回答