太阳教程中有关死锁的问题

7
以下是直接从Sun教程中描述死锁的代码。然而,考虑到两种方法都是同步的,我不明白如何在这种情况下会发生死锁。两个线程如何同时进入相同的同步方法?
死锁是指两个或多个线程永久地阻塞,等待彼此。以下是一个例子:
阿方斯和加斯顿是朋友,他们非常注重礼貌。礼貌的严格规则是当你向朋友鞠躬时,必须一直保持弯腰的姿势,直到你的朋友有机会回敬鞠躬。不幸的是,这个规则没有考虑到两个朋友可能同时向对方鞠躬的可能性。这个示例应用程序Deadlock模拟了这种可能性:
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();
    }
}

当死锁发生时,两个线程都尝试调用bowBack时很可能会被阻塞。由于每个线程都在等待另一个线程退出bow,因此没有任何一个阻塞将会结束。


1
可能是重复的问题:试图理解线程死锁的原理 - Oleg Estekhin
1个回答

8

Synchronized (instance)方法在对象上加锁,而不是在类上加锁。

alphonse.bow获取了alphonse的锁,gaston.bow获取了gaston的锁。当“alphonse”线程在bow时,它会尝试在bower.bowBack上获取“gaston”的锁。同样,“gaston”也会尝试获取“alphonse”的锁。

为了更好的阐述(希望如此):

我们将这两个线程称为Thread1和Thread2。

Thread1运行alphonse.bow(gaston)以获取alphonse对象上的锁,而Thread2运行gaston.bow(alphonse)来获取gaston对象上的锁。

在Thread1中,当它尝试运行bower.bowBack(this),其中bower = gaston时,该线程需要首先获得gaston的锁。

同时,Thread2也尝试执行相同的操作,其中bower = alphonse。Thread1拥有Thread2需要的锁,反之亦然,这就是死锁发生的原因。

顺便说一句,死锁并不总是会发生。如果Thread1能够在Thread2有机会运行之前开始并完成(例如,如果某些事情在Thread1启动但Thread2被创建/启动之前挂起主线程),则不会发生死锁。


阿方斯如何尝试在加斯顿上锁。阿方斯始终使用阿方斯实例。 - jax
我不理解以下内容:“当'alphonse'线程处于弓形状态时,它会尝试在bower.bowBack上获取'gaston'实例的锁。” 'alphonse'如何尝试获取gaston实例的锁? - jax
第一个线程调用alphonse.bow(gaston)。此时,该线程对alphonse进行了锁定。一旦该方法到达bower.bowBack,其中bower是gaston,它会尝试获取gaston的锁定。我所说的“alphonse”是指运行“alphonse.bow(gaston)”的线程;如果不清楚,请见谅。 - PH.
啊...我明白了。在 bower.bowBack(this) 中,bower = gaston - 出于某种原因,我一直以为 bower 是 alphonse。 - jax
我已经多次运行了这个Java文档示例,每次都能正常工作。 执行输出如下:Alphonse: Gaston向我鞠躬! Gaston: Alphonse向我回鞠躬! Gaston: Alphonse向我鞠躬! Alphonse: Gaston向我回鞠躬!我有什么遗漏吗? - MTB

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