Многопоточность с помощью System.out.format и System.out.println

Я наткнулся на этот пример в Oracle Java Tutorial, описывающий взаимоблокировку в нескольких потоковые сценарии.

Итак, в этом примере я сделал следующие изменения в строке 17 и строке 18.

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) {
        //My Changes
        //System.out.format("%s: %s" + " has bowed to me!%n", this.name, bower.getName()); //Line 17
         System.out.println(this.name + ": " + bower.getName() + " has bowed to me!"); //Line 18
        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() {
            alphonse.bow(gaston);
        }
    }).start();

    new Thread(new Runnable() {
        @Override
        public void run() {
            gaston.bow(alphonse);
        }
    }).start();
  }
}

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

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

Итак, мой вопрос: почему он так себя вел? Как оператор println предотвратил взаимоблокировку?


person Steve    schedule 25.10.2016    source источник
comment
Я не вижу, как это будет. System.out.format на самом деле ничем не отличается от System.out.println в отношении блокировки.   -  person Andy Turner    schedule 25.10.2016
comment
Это не имеет значения - просто взаимоблокировка зависит от чередования потоков, которое будет отличаться между запусками. Если вы запустите его несколько раз с System.format, вы, вероятно, будете время от времени наблюдать правильный вывод. И с println вы также увидите запуски, в которых программа блокируется.   -  person assylias    schedule 25.10.2016
comment
Действительно: первый вариант может нормально работать на ideone.   -  person Andy Turner    schedule 25.10.2016
comment
@AndyTurner Возможно ли, что я сделал что-то не так, внеся эти изменения?   -  person Steve    schedule 25.10.2016
comment
@ Стив, я думаю, что твоя интерпретация неверна; см. комментарий assylias.   -  person Andy Turner    schedule 25.10.2016


Ответы (4)


Нет никакой разницы, используете ли вы System.out.print или System.out.format: они в основном делают одно и то же.

Тупик возникает здесь, если выполнение Gaston.bow(Alphonse) начинается между запуском Alphonse.bow(Gaston) и bower.bowBack(Alphonse) (или наоборот): два потока ожидают монитора, удерживаемого другим, и, таким образом, возникает тупик.

Это происходит непоследовательно, потому что это зависит от тонкой проблемы синхронизации, в зависимости от того, как запланированы потоки - возможно, что Alphonse.bow и bower.backBack(Alphonse) завершатся до выполнения Gaston.bow, поэтому похоже, что взаимоблокировки нет.

Классический способ исправить это — упорядочить получение блокировки таким образом, чтобы каждый раз первой получалась одна и та же блокировка; это предотвращает возможность взаимоблокировки:

public void bow(Friend bower) {  // Method no longer synchronized.
  int firstHash = System.identityHashCode(this);
  int secondHash = System.identityHashCode(bower);

  Object firstMonitor = firstHash < secondHash ? this : bower;
  Object secondMonitor = firstHash < secondHash ? bower : this;
  synchronized (firstMonitor) {
    synchronized (secondMonitor) {
      // Code free (*) of deadlocks, with respect to this and bower at least.
    }
  }
}

(*) Не гарантируется полностью отсутствие взаимоблокировок, поскольку System.identityHashCode может возвращать одно и то же значение для разных объектов; но это маловероятно.

Это применение парадокса дня рождения: если у вас всего два монитора, вероятность столкновения примерно 10^-18; но если у вас > 77 тыс. мониторов, коллизия более вероятна, чем нет.

person Andy Turner    schedule 25.10.2016

Вы тут путаете вещи.

Тот факт, что фрагмент кода может привести к ситуации взаимоблокировки, не обязательно означает, что вы получаете взаимоблокировку каждый раз при выполнении этого кода.

Это один из аспектов, который делает многопоточность такой сложной темой: если вы запустите свой код один раз, или 10 раз, или 100 раз, и все «работает»; все еще возможно, что в следующий раз он потерпит неудачу.

Другими словами: попробуйте поместить этот код в цикл на самом внешнем уровне, и рано или поздно (вероятно, раньше, если вы не будете много «спать») вы должны зайти в тупик!

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

person GhostCat    schedule 25.10.2016

Чтобы поддержать остальные ответы здесь некоторыми фактическими доказательствами, я запустил ваш код в цикле, и он зашел в тупик 82 раза из 100 попыток, поэтому ваш код определенно все еще находится в тупике.

person Raniz    schedule 25.10.2016
comment
Я не согласен с этим. Вопрос в том, как оператор println предотвратил взаимоблокировку? и мой ответ, что это не так. Я могу согласиться с тем, что сам по себе он не является ответом, поскольку я ссылаюсь на другие ответы. - person Raniz; 07.12.2017
comment
Имеет смысл ;-) - person GhostCat; 07.12.2017

Тупик вообще не зависит от функции println. Это вызвано тем, что два потока пытаются получить доступ друг к другу и блокируются друг другом.

Изменение формата на println приведет к достаточной задержке в программе, чтобы позволить потокам блокировать друг друга без конфликтов, то есть взаимоблокировок. Так что вы на самом деле не исправили это; вы только что добавили некоторую задержку, что означает, что потоки не блокируются.

person DrUseful    schedule 25.10.2016