Вопрос о тупиковой ситуации на Java - PullRequest
11 голосов
/ 14 октября 2009

Я узнаю о взаимоблокировках в Java, и вот пример кода из официального руководства Sun:

Альфонс и Гастон - друзья, и великие верующие в вежливости. Строгий Правило вежливости в том, что когда ты кланяешься другу, ты должен оставаться склоненным пока у вашего друга не будет возможности верните лук. К сожалению это правило не учитывает вероятность того, что двое друзей могут поклониться друг к другу одновременно.

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

Вот объяснение Солнца:

Когда работает Deadlock, это очень вероятно, что оба потока будут блокироваться когда они пытаются вызвать BowBack. Ни один блок никогда не закончится, потому что каждый поток ждет другого выйти из лука.

Кажется, я не совсем понимаю. Когда alphonse.bow (gaston) работает, метод смычка заблокирован. Так что теперь он сначала напечатает «Гастон поклонился мне!». Затем он продолжит и вызовет bowBack, а также заблокирует bowBack. Как это может вызвать тупик? Я неправильно понимаю, что происходит, когда вызывается синхронизированный метод?

Если кто-то может дать мне простое объяснение, спасибо.

Ответы [ 6 ]

27 голосов
/ 14 октября 2009

Важно отметить, что заблокированы не методы , а экземпляры объектов .

Когда вы звоните alphonse.bow(gaston), он пытается установить блокировку на alphonse. Получив блокировку, он печатает сообщение, а затем вызывает gaston.bowBack(alphonse). В этот момент он пытается получить блокировку на gaston. Получив блокировку, он печатает сообщение, затем снимает блокировку и, наконец, блокировка на alphonse снимается.

В тупике блокировки приобретаются в таком порядке, что ни один из потоков не может продолжить.

  • Тема 1: получает блокировку на alphonse
  • Тема 2: получает блокировку gaston
  • Тема 1: печатает сообщение
  • Поток 1: пытается получить блокировку на gaston - не может, потому что Поток 2 уже имеет его.
  • Тема 2: печатает сообщение
  • Поток 2: пытается получить блокировку на alphonse - не может, потому что Поток 1 уже имеет ее.
8 голосов
/ 14 октября 2009

альфонс и гастон - два разных объекта. Каждый объект имеет встроенный монитор (замок), связанный с ним.

Это может произойти так:

Альфонс создан. Его объект монитора равен 1.

гастон создан. Его объект монитора составляет 2.

alphonse.bow (Гастон); Альфонс теперь владеет замком # 1

gaston.bow (Альфонс); Гастон теперь владеет замком # 2

Альфонс вызывает bowBack на гастоне и ждет блокировки №2. Гастон вызывает BowBack на Alphonse и ждет блокировки # 1

Имеет смысл? Использование синхронизированных блокировок ключевых слов, которые экземпляры отслеживают на протяжении всего метода. Пример можно переписать следующим образом:

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(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());
            }
        }
    }
}
1 голос
/ 14 октября 2009

Добавить к Симонну и др.,

Если вы хотите визуализировать это, скомпилируйте и запустите программу и создайте дамп потока работающей программы. Вы можете сделать это, набрав Ctrl - Break на консоли Windows или введя команду kill -QUIT [pid] для системы * nix. Это предоставит вам список всех Threads, работающих в вашей системе, и где они работают или ожидают, а также мониторы, в которых потоки либо блокируются, либо ожидают блокировки.

Если вы измените имена потоков в их конструкторах, вам будет проще найти их в дампе полного потока:

   new Thread(new Runnable() {
        public void run() { alphonse.bow(gaston); }
    }, "Alphonse").start();
    new Thread(new Runnable() {
        public void run() { gaston.bow(alphonse); }
    }, "Gaston").start();
1 голос
/ 14 октября 2009

Блокировки хранятся на объектах Java, а не на методах Java. Поэтому, когда синхронизированный метод используется для метода, он блокирует объект «this». В случае статического метода он блокирует объект класса.

Вы можете явно указать объект монитора, используя synchronized ( object ) { }

0 голосов
/ 14 октября 2009

, синхронизированный в определении метода, является сокращением для синхронизации самого объекта (этого). По сути это означает, что bow () и bowBack () не могут быть вызваны на одном и том же объекте Friend.

Теперь, если оба Друзья попадут в bow (), ни один из них не сможет вызвать метод bowBack () друг друга.

0 голосов
/ 14 октября 2009

Мне придется перепроверить, но я думаю, что синхронизированный метод блокирует объект класса, поэтому он блокирует другие синхронизированные методы в том же классе.

Я думаю, что он блокирует сам объект класса, поэтому он даже блокирует разные экземпляры.

Изменить, чтобы добавить:

Взгляните на эту часть спецификации языка Java

Каждый метод лука захватывает свой собственный монитор объектов. Затем оба пытаются отозвать поклон другого объекта назад и заблокировать ожидание другого монитора.

...