1

这是来自 java 文档的死锁教程。我不知道线程在哪里被阻塞。由于它是同步的,我认为只有一个线程会进入弓。但都进入了弓。[一个等待[但什么时候?]]

那么问题出在哪里?

当我添加评论时[打印语句以跟踪]。没有僵局。如何?

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() {
            @Override
            public void run() { 
               // System.out.println("Thread 1");
                alphonse.bow(gaston); 
               // System.out.println("Th: gaston bowed to alphonse");
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() { 
              //  System.out.println("Thread 2");
                gaston.bow(alphonse);
              //  System.out.println("2.gaston waiting alph bowed");
            }
        }).start();
    }
}

输出是:

Alphonse: Gaston  has bowed to me!
Gaston: Alphonse  has bowed to me!

没有人退缩!

4

5 回答 5

7

这里有两件重要的事情要理解:1)每个并发运行的线程在做什么?2) 涉及哪些锁?

最后一件事,您创建了 Friend 类的两个实例:alphonse 和 gaston。这些对象中的每一个都拥有自己的锁。所以有两种锁:alphonse 的锁和gaston 的锁。当您输入对象的同步方法时,它会锁定该对象的锁。当同步方法返回时,锁被释放(解锁)。

现在是线程。您的第一个线程,我们称之为 Alphone 的线程(注意大写 A 以区分线程和对象,alphonse)这样做:(“A:”表示这是 Alphonse 的线程。)

A: alphonse.bow(gaston) - acquires alphonse's lock
A: gaston.bowBack(alphonse) - acquires gaston's lock
A: both methods return, thus releasing both locks

同时,Gaston 的线程执行此操作:(“G:”表示这是 Gaston 的线程。)

G: gaston.bow(alphonse) - acquires gaston's lock
G: alphonse.bowBack(gaston) - acquires alphonse's lock
G: both methods return, thus releasing both locks

现在我们可以将这两个信息放在一起得出我们的答案。线程可以以不同的顺序交错(即它们的事件发生)。例如,如果事件按以下顺序发生,则会发生死锁:

A: alphonse.bow(gaston) - acquires alphonse's lock
G: gaston.bow(alphonse) - acquires gaston's lock
G: attempts to call alphonse.bowBack(gaston), but blocks waiting on alphonse's lock
A: attempts to call gaston.bowBack(alphonse), but blocks waiting on gaston's lock to

在这种情况下,每个线程都被阻塞,等待获取另一个线程持有的锁。然而,两个线程都不会释放它持有的锁,因为它必须完成它的方法才能这样做,它不能这样做,因为它被阻塞在另一个线程上等待。因此,它们被卡在了原地——陷入僵局。

但是,另一种可能的交错(事件顺序)是其中一个线程在另一个线程真正开始之前完成,如下所示:

A: alphonse.bow(gaston) - acquires alphonse's lock
A: gaston.bowBack(alphonse) - acquires gaston's lock
A: both methods return, thus releasing both locks
G: gaston.bow(alphonse) - acquires gaston's lock
G: alphonse.bowBack(gaston) - acquires alphonse's lock
G: both methods return, thus releasing both locks

在这种情况下,没有死锁。当您添加 println 并且没有死锁时,很可能发生的情况是额外 println 的额外延迟会影响线程运行的调度和/或速率,因此您会得到这种交错而不是死锁。

当结果取决于并发事件的顺序(即它们的调度或它们运行的​​速率)时,称为“竞争条件”。例如,在这个程序中,哪个线程首先打印输出取决于它们被调度的顺序或它们运行的​​速率。线程是否死锁也取决于此。因此,通过扰乱该速率(例如,通过添加额外指令),您可能会影响比赛的结果——但这并不意味着比赛已经结束。

并非所有竞争条件都有可能导致死锁。但是,根据我的经验,死锁只发生在竞争条件下。(也许知道的人可以评论这是否必然如此,或者只是通常情况?)

死锁一开始并不容易理解;我已尽力解释,但如果您对我的帖子有任何后续问题,请告诉我。:)

于 2013-07-01T04:30:52.923 回答
5

首先,调用System.out.println()会带来大量资源,因此会在处理过程中产生有效延迟,从而人为地导致/避免并发问题。不要过多地阅读添加打印的效果。

现在,当线程 A 锁定 X 然后锁定 Y,同时线程 B 锁定 Y 然后锁定 X 时,就会发生死锁。如果两者都获得了第一个锁定,则两者都不能继续 = 死锁。

在您的情况下,该方法bow()锁定了它被调用的实例,但随后从该锁中调用 bowee 的 bow 方法:同步正在锁定实例,因此两个单独的实例都可以获得它们的第一个锁。第二个锁在另一个实例上,所以两个线程都在等待另一个线程持有的锁=死锁。

于 2013-07-01T03:20:19.593 回答
0

您在那里有 2 个单独的对象 - synchronized 关键字不会阻止在对象 1 和对象 2 中同时调用 bow。它只会阻止两个线程进入同一对象的同步方法。

因此,然后输入了两种方法,并在每个对象上都持有锁。然后两者都不能输入另一个对象的方法,因为同步关键字在对象级别上起作用。

于 2013-07-01T03:16:09.600 回答
0

考虑在某个时间点,“Alphonse”和“Gaston”都开始鞠躬,这意味着他们已经分别拥有了自己的锁。然后他们都希望对方同时鞠躬,这意味着他们都试图获得对方的锁。由于两个锁都被占用,bowBack()调用将被阻塞。并且因为bowBack()bow()块内,bow()函数不能立即返回。因此,当他们已经获得了一些资源,但仍然试图获得一些已经占用的其他资源时,他们处于死锁状态。

于 2013-07-01T03:42:54.813 回答
-2

这里解释得很清楚。由于第一个对象试图在第二个对象上调用某些东西,第二个对象在进入同步块后一直在等待,所以第一次对 bowBack() 的调用将永远不会结束,并且两个调用都被卡在那里。阅读文章。第一次我没有注意到你是bower.getName()从对面的对象打电话来的。bower.getName()调用导致死锁

于 2013-07-01T03:23:31.630 回答