Вопрос о блокировке 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() {
            public void run() { alphonse.bow(gaston); }
        }).start();
        new Thread(new Runnable() {
            public void run() { gaston.bow(alphonse); }
        }).start();
    }
}

Ответ 1

Вот как он, вероятно, будет выполнен.

  • Введите alphonse.bow(gaston);, теперь привязка заблокирована из-за ключевого слова synchronized
  • Введите gaston.bow(alphonse);, теперь заблокирован газон
  • Не удается выполнить bower.bowBack(this); с первого вызова метода bow, потому что галон (беседка) заблокирован. Подождите, пока блокировка не будет выпущена.
  • Не удается выполнить bower.bowBack(this); из второго метода вызова bow, потому что alphonse (bower) заблокирован. Подождите, пока блокировка не будет выпущена.

Оба потока ждут друг друга, чтобы освободить блокировку.

Ответ 2

Рассмотрим следующее:

  • Пусть Thread1 run() { alphonse.bow(gaston); }
  • Пусть Thread2 run() { gaston.bow(alphonse); }
  • Thread1 входит alphonse.bow(gaston);, блокировка alphonse, так как bow() есть synchronized
  • Thread2 входит gaston.bow(alphonse);, блокировка gaston, так как bow() есть synchronized
  • В Thread1, bower.bowBack(this); оценивается как gaston.bowBack(alphonse);
    • Thread1 пытается получить блокировку для gaston, которая в настоящее время поддерживается Thread2
  • В Thread2, bower.bowBack(this); оценивается как alphonse.bowBack(gaston);
    • Thread2 пытается получить блокировку для alphonse, которая в настоящее время поддерживается Thread1
  • каждый поток ожидает, что другой отпустит блокировку, следовательно, тупик

Проблема в том, что в настоящее время существует чрезмерное synchronized. Существует много способов "исправить" это; здесь поучительное решение:

    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 synchronized void bowBack(Friend bower) {
        System.out.format("%s: %s has bowed back to me!%n",
                this.name, bower.getName());
    }

Теперь bowBack() полностью synchronized, но bow() частично synchronized частично, используя оператор synchronized(this). Это предотвратит тупик.

Вот цитаты из Effective Java 2nd Edition, пункт 67: Избегайте чрезмерной синхронизации

Чтобы избежать сбоев в работе и безопасности, никогда не уступайте управление клиенту в рамках метода или блока synchronized. Другими словами, внутри области synchronized не вызывать метод, предназначенный для переопределения или предоставляемый клиентом в виде функционального объекта. С точки зрения class с областью synchronized такие методы чужды. Класс не знает, что делает этот метод и не контролирует его. В зависимости от того, что делает инородный метод, вызов его из области synchronized может вызвать исключения, взаимоблокировки или повреждение данных.

[...] Как правило, вы должны делать как можно меньше работы внутри регионов synchronized. Получите блокировку, проверьте общие данные, при необходимости измените их и отпустите блокировку.

По существу, bower.bowBack(this) - это попытка уступить управление чуждому методу, потому что bowBack() не является final методом в class Friend. Рассмотрим следующую попытку исправить проблему, например:

    // attempt to fix: STILL BROKEN!!!

    public synchronized void bow(Friend bower) {
        System.out.format("%s: %s has bowed to me!%n", 
            this.name, bower.getName());
        bower.bowBack(this);
        // ceding control to alien method within synchronized block!
    }

    // not a final method, subclasses may @Override
    public void bowBack(Friend bower) {
        System.out.format("%s: %s has bowed back to me!%n",
                this.name, bower.getName());
    }

Вышеприведенный код не будет блокироваться с текущим сценарием alphonse/gaston, но поскольку bow() привязывает элемент управления к методу final bowBack(), подкласс может @Override метод таким образом, чтобы выведите bow() в тупик. То есть bowBack() является чужой метод bow() и поэтому НЕ должен быть вызван из области synchronized.

Ссылки

См. также

  • Эффективное Java 2nd Edition
    • Пункт 66: Синхронизация доступа к совместно используемым изменяемым данным
    • Пункт 15: Минимизировать изменчивость

Ответ 3

Лучший способ понять - поставить ниже код в лук() перед вызовом bower.bowBack

try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}