Почему перед вызовом метода ожидания объекта поток должен иметь монитор точно такого же объекта? - PullRequest
0 голосов
/ 07 октября 2018

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

Так что для вызова a.wait() сначала необходимо выполнить синхронизацию на объекте a или, если быть более точным, поток должен стать владельцем * 1005.* монитор.Насколько я понимаю, единственная причина, по которой a.wait() следует вызывать в синхронизированном контексте (вместе с a.notify() / a.notifyAll()), состоит в том, чтобы избежать условия гонки / Потерянная проблема пробуждения ,Но теоретически можно избежать состояния гонки, вызывающего a.wait(), a.notify()/a.notifyAll(), путем синхронизации на каком-либо другом объекте, например:

Thread #1: 
synchronized(b) { 
    …
    a.wait(); // here a thread owns only one monitor: the monitor of the object stored in the variable b, where a != b
    …
}

Thread #2: 
synchronized(b) {
    …
    a.notify(); // the same here
    …
}

Ожидаемое поведение: поток # 1 получает мониторb, входит в критическую секцию, вызывает a.wait(), затем ждет a и освобождает монитор b.После этого поток № 2 получает монитор b, входит в критическую секцию, вызывает a.notify() (поток № 1 получает уведомление), выходит из критической секции и освобождает монитор b, затем поток № 1 получаетмонитор b снова и продолжает работать.Таким образом, одновременно может работать только одна критическая секция, синхронизированная на b, и не может возникнуть состояние гонки.Вы можете спросить: «Но как бы wait() узнал, что объект b используется в этом случае, чтобы избежать состояния гонки и, следовательно, его монитор должен быть отпущен?».Ну, так оно и не узнает.Но, возможно, метод wait мог бы взять объект в качестве аргумента, чтобы узнать, какой монитор объекта нужно освободить, например, a.wait(b), чтобы ждать на a и освободить монитор b.Но такой опции нет: a.wait() заставляет поток ждать a и освобождает монитор с точно таким же a.Период.Вызов методов wait и notify в приведенном выше коде приводит к IllegalMonitorStateException .

Согласно этой информации из статьи Википедии , функциональностьМетоды wait, notify/notifyAll встроены в монитор каждого объекта, где они интегрированы в функциональность механизма синхронизации на уровне каждого отдельного объекта.Но все же это не объясняет, почему это было сделано именно таким образом.

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

Давайте посмотрим, есть ли ситуации, в которых может быть нежелательно синхронизироваться на объекте раньшевызывая его методы wait, notify/notifyAll (плюс некоторая соответствующая логика).Синхронизация на объекте блокирует его для других потоков, что может быть нежелательно, если у объекта есть некоторые другие синхронизированные методы (и / или есть некоторые критические секции, синхронизированные на объекте), не имеющие ничего общего с логикой ожидания.В этом случае всегда можно выполнить рефакторинг соответствующего класса (классов), чтобы методы wait и notify/notifyAll работали с одним объектом, а другие синхронизированные методы / блоки - с другим.Например, одним из возможных решений может быть создание выделенного объекта специально для логики ожидания (для ожидания и синхронизации), как в примере ниже:

public class A {
    private Object lock = new Object(); // an object to synchronize and wait on for some waiting logic
    private boolean isReady = false;

    public void mOne() throws InterruptedException {
        synchronized(lock) { // it blocks the instance of the Object class stored in the variable named lock
            while(!isReady) {
                lock.wait();
            }
        }
    }

    public void mTwo() {
        synchronized(lock) { // the same here
            isReady = true;
                lock.notify();
        }
    }

    synchronized public void mThree() { // it blocks an instance of A
        // here is some logic having nothing to do with the above waiting and
        // mThree() can run concurrently with mOne() and mTwo() 
    }
}

Так что нет необходимости синхронизировать некоторые произвольныеобъект, чтобы избежать определенного состояния гонки относительно вызова wait, notify/notifyAll методов.Если бы это было разрешено, это только вызвало бы ненужную путаницу.

Это правильно?Или, может быть, я что-то здесь упускаю.Я хотел бы убедиться, что не пропустил ничего важного.

Документация Oracle: wait , notify , notifyAll

...