@WTFAYD

Для чего в блоке synchronized указан другой монитор (не this)?

Ниже находится фрагмент кода из учебника "Философия Java". В нем кратко описывается концепция producer-consumer в многопоточности.

Общая суть такова, в ресторане (Restaurant) есть один повар (Chef) и один официант (WaitPerson). Официант ждет (wait), пока повар приготовит блюдо (Meal). Когда повар блюдо приготовил, он оповещает (notify) об этом официанта, который получает блюдо, относит его клиенту и снова начинает ждать.

Подскажите пожалуйста, почему в качестве объекта для второго блока synchronized, в котором находится метод notifyAll() (например, это rest.chef в классе WaitPerson), используется не this, как в первом случае? Есть идея, что это из-за того, что нужно разбудить именно повара, а не официанта, хочется развеять сомнения.

class WaitPerson implements Runnable {
    private Restaurant rest;

    public WaitPerson(Restaurant r) {
        rest = r;
    }

    @Override
    public void run() {
        try {
            while(!Thread.interrupted()) {
                synchronized (this) {
                    while (rest.meal == null)
                        wait(); // ... for the chef to produce a meal
                }
                System.out.println("Waitperson got " + rest.meal);
                synchronized (rest.chef) {
                    rest.meal = null;
                    rest.chef.notifyAll(); // ready for another
                }
            }
        } catch(InterruptedException e) {
            System.out.println("WaitPerson interrupted");
        }

    }
}

class Chef implements Runnable {
    private Restaurant rest;
    private int count = 0;

    public Chef(Restaurant r) {
        rest = r;
    }

    @Override
    public void run() {
        try {
            while(!Thread.interrupted()) {
                synchronized (this) {
                    while(rest.meal != null)
                        wait(); // ... for the meal to be taken
                }
                if(++count == 10) {
                    System.out.println("Out of food");
                    rest.exec.shutdownNow();
                }
                System.out.print("Order up! ");
                synchronized (rest.waitPerson) {
                    rest.meal = new Meal(count);
                    rest.waitPerson.notifyAll();
                }
                TimeUnit.MILLISECONDS.sleep(100);
            }

        } catch(InterruptedException e) {
            System.out.println("Chef interrupted");
        }
    }
}
  • Вопрос задан
  • 320 просмотров
Решения вопроса 1
pi314
@pi314
Президент Солнечной системы и окрестностей
Так и есть, причем, у обоих )) Фактически, оба конкуррируют за один ресурс rest.meal, но не "глобально", а раздельно за две разные операции с ним - в первом блоке на чтение, во втором на запись. Блоком synchronize(this) каждый из них вешает монитор на себя, любимого, выполняет чтение и начинает ждать пинка от другого. Вторым же блоком он вешает монитор на другого, выполняет запись и пинает другого. Это можно бы было написать несколько элегантнее и читабельнее с использованием более высокоуровневых абстракций из java.util.concurrent (где под капотом происходило бы почти то же самое), но суть примера, насколько я понимаю, именно в том, чтоб показать, как в такой ситуации целенаправленно синхронизировать два потока друг с другом непосредственно, так, чтоб каждый только один раз читал и один раз писал.

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

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы