Вопрос о тупиковой ситуации в Java

Я изучаю взаимоблокировки на 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. Ни один блок никогда не закончится, потому что каждая нить ждет другого для выхода из лука.

Я не совсем понимаю. Когда альфонс.бот (гастон) работает, метод лука заблокирован. Итак, теперь он сначала напечатает "Гастон поклонился мне!". Затем он продолжится и вызовет bowBack, а также заблокирует bowBack. Как это может вызвать тупик? Я не понимаю, что происходит при вызове синхронного метода?

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

Ответ 1

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

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

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

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

Ответ 2

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

Это может произойти следующим образом:

Создается символ

. Его объектный монитор равен 1.

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

alphonse.bow(Гастон); Теперь alphonse владеет блокировкой # 1

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

alphonse вызывает bowBack на гадоне и ждет блокировки # 2 газон вызывает bowBack на алфавит и ждет блокировки # 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());
            }
        }
    }
}

Ответ 3

Замки хранятся на объектах Java, а не на Java. Поэтому при синхронизации используется метод, он блокирует объект "this". В случае статического метода он блокирует объект класса.

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

Ответ 4

Чтобы добавить к simonn, и др.,

Если вы хотите визуализировать это, скомпилируйте и запустите программу и создайте дамп потока запущенной программы. Вы можете сделать это, набрав 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();

Ответ 5

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

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

Ответ 6

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

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

Изменить для добавления:

Взгляните на эту часть спецификацию java language

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