Как System.out.format может предотвратить взаимоблокировку?

Я обнаружил, что включение вызова System.out.format в классический Учебное пособие по взаимоблокировке Java предотвратит взаимоблокировку, и я не могу понять, почему.

Приведенный ниже код такой же, как и в учебнике, с добавлением main из System.out.format("Hi, I'm %s...no deadlock for you!\n\n", alphonse.getName());.

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) throws InterruptedException {
        final Friend alphonse = new Friend("Alphonse");
        final Friend gaston = new Friend("Gaston");

        System.out.format("Hi, I'm %s...no deadlock for you!\n\n", alphonse.getName());

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

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

Вот результат:

Hi, I'm Alphonse...no deadlock for you!

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!

Удаление оскорбительной строки приводит к обычному тупику:

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

Изменяет ли как-то вызов System.out.format способ, которым потоки получают встроенные блокировки объектов?

Обновлять:

Я смог снова заставить систему зайти в тупик, просто изменив, где я запускаю потоки в коде:

public static void main(String[] args) throws InterruptedException {
    final Friend alphonse = new Friend("Alphonse");
    final Friend gaston = new Friend("Gaston");

    System.out.format("Hi, I'm %s...no deadlock for you!\n\n", alphonse.getName());

    Thread t1 = new Thread(new Runnable() {
        public void run() { alphonse.bow(gaston); }
    });

    Thread t2 = new Thread(new Runnable() {
        public void run() { gaston.bow(alphonse); }
    });

    t1.start();
    t2.start();
}

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


person Bobby Norton    schedule 16.09.2012    source источник
comment
Когда вы добавляете эту строку, первый поток выполняется до того, как основной поток запустит второй поток.   -  person Bhesh Gurung    schedule 17.09.2012
comment
Но это только по совпадению на одном конкретном процессоре и планировщике. Еще нет гарантии, что взаимоблокировка не произойдет.   -  person Wyzard    schedule 17.09.2012


Ответы (2)


На самом деле вы не удалили взаимоблокировку, а скорее (из-за какой-то внутренней причины JVM) изменили время потоков, чтобы один из потоков вошел в bowBack() до другого вызова bow() . Просто введите bow: sleep(1000), и ваш тупик снова появится.

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

... И "какая-то внутренняя причина JVM" может быть следующей:

В вашем случае на самом деле есть три потока: один, выполняющий main, t1 и t2. Причина, по которой установка print скрывает взаимоблокировку, может заключаться в том, что планировщик потоков решил, что main еще нужно выполнить работу, т. е. очистить буферы ввода-вывода, и поэтому позволил main продолжить работу после запуска. t1 и до начала t2. Если у вас двухъядерный процессор, будут работать только main и t1, но t2 будет ждать, так как print — медленная операция. Переключение контекста займет некоторое время, и t1 завершится до того, как сможет начаться t2... так что взаимной блокировки не произойдет. Но это не значит, что если вы снова запустите программу, взаимоблокировка не произойдет.

Если вы хотите поиграть, создайте queue и поместите токены (имена потоков) в эту очередь, а затем join ваши потоки в main. После их завершения распечатайте содержимое очереди, и вы сможете наблюдать за синхронизацией потоков.

person Op De Cirkel    schedule 16.09.2012

format() и запись в консоль обычно являются дорогостоящими операциями. Я предполагаю, что его выполнение изменяет время запуска потоков, так что второй поток запускается так поздно, что не мешает первому.

person gpeche    schedule 16.09.2012