Java ждет/уведомляет - не просыпается поток

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

Мой код:

public class Simple{
    static final Thread mainThread = Thread.currentThread();

    public static void main(String[] args) throws InterruptedException {
        PrintThread printer = new PrintThread(0);
        printer.start();

        synchronized (mainThread){
            System.out.println("main sleeping while waiting for printer to be started");
            mainThread.wait();
            System.out.println("main woke up");


            for (int i = 0; i < 1000; i++) {
                synchronized (printer){
                    System.out.println("added num "+i);
                    printer.numToPrint = i;
                    System.out.println("main waking up printer");
                    printer.notifyAll();
                    System.out.println("main sleeping");
                    mainThread.wait();
                    System.out.println("main woke up");
                }
            }

        }

    }
}

class PrintThread extends Thread{
    public int numToPrint = -1;

    public PrintThread(int numToPrint){
        this.numToPrint = numToPrint;
    }

    @Override
    public synchronized void run() {
        System.out.println("printer started");
        while (true){
            try {
                synchronized (Simple.mainThread){
                    System.out.println("printer waking up main");
                    Simple.mainThread.notifyAll();
                }
                System.out.println("printer sleeping");
                wait();
                System.out.println("printer woke up");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("printing num "+numToPrint);
        }
    }

}

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

main sleeping while waiting for printer to be started
printer started
printer waking up main
printer sleeping
main woke up
added num 0
main waking up printer
main sleeping
printer woke up
printing num 0
printer waking up main
printer sleeping
main woke up
added num 1
...

Вместо этого это делает:

main sleeping while waiting for printer to be started
printer started
printer waking up main
printer sleeping
main woke up
added num 0
main waking up printer
main sleeping

Итак... кажется, что notify не будит поток принтера?

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

Что я делаю неправильно?


person Fede Capece    schedule 14.05.2020    source источник


Ответы (2)


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

ГОРАЗДО лучшим способом добиться этого было бы создание общего третьего общего объекта, на котором оба потока могли бы получить блокировку. Это синхронизирует оба потока, пока они ждут доступа к этому объекту.

Кроме того, вы должны прочитать Javadocs для Thread.wait, notify и notifyAll. Если/когда вы это сделаете, вы увидите, что вам НИКОГДА не следует вызывать эти методы в потоках, поскольку они используются при выполнении thread.join (не только это, но и моя «претензия на славу», я считаю, что это был мой запрос об ошибке много лет назад чтобы задокументировать это, когда этого не было в JavaDoc, что привело к его добавлению в Javadoc. Мог быть кто-то другой, но это произошло сразу после того, как я попросил об этом :))

person ControlAltDel    schedule 14.05.2020
comment
Ммм... это имеет смысл, если основной вызов уведомляет до того, как принтер перейдет в спящий режим, тогда вся программа будет заблокирована. Но... в выводе говорится, что ожидание вызывается первым... Так возможно ли, что ожидание начинает выполнение, но не завершает выполнение до начала уведомления? - person Fede Capece; 14.05.2020
comment
@FedericoCapece, как я уже сказал, вы делаете это неправильно, и вы никогда не заставите это работать так, как вы пытаетесь - person ControlAltDel; 14.05.2020
comment
только что попробовал синхронизировать один объект вместо синхронизации двух разных потоков, и... это сработало, большое спасибо! - person Fede Capece; 14.05.2020

Свойство: вызов wait() снимает блокировку (за которой он отслеживал) и переходит в состояние ожидания. Он ожидает notify() или notifyAll() для того же объекта. После того, как notify() или notifyAll() были запланированы в ЦП, они снова получают блокировку перед возобновлением.

Когда вы впервые выполнили «синхронизацию (mainThread)» в основном методе, он в основном заблокировал объект класса «mainThread». Когда вызывается mainThread.wait(), mainThread переходит в состояние ожидания (ожидание, когда кто-то вызовет notify или notifyAll для объекта класса mainThread).

К этому времени PrintThread может получить процессор. Это когда «синхронизированный (Simple.mainThread)» назначается и блокирует «Simple.mainThread» и уведомляет все потоки, ожидающие «Simple.mainThread». Здесь сразу после завершения этого блока PrintThread снимает блокировку «Simple.mainThread».

В этот момент основной поток попытается снова получить блокировку «mainThread», прежде чем возобновить работу с того места, где было вызвано ожидание. Поскольку к этому моменту блокировка «mainThread» не получена, основной поток получает блокировку и печатает «основной проснулся».

Здесь встречается цикл for. Помните: здесь блокировка объекта класса mainThread уже получена.

Теперь внутри цикла for он получает блокировку объекта «принтер». Выполняет некоторые вычисления и вызывается «printer.notifyAll()», и все потоки, ожидающие объекта «принтер», будут уведомлены.

** Здесь следует помнить следующее: поскольку курсор кода все еще находится внутри «синхронизированного (принтера)», блокировка объекта «принтер» еще не снята. **

Двигаясь вперед, печатается «основной сон», а затем вызывается «mainThread.wait()». Это пытается получить блокировку на «mainThread», который уже получен (упомянутый выше, где «Помните:» в блоке), и застревает, поскольку ни один поток не уведомляет о «mainThread» в дальнейшем, а блок «синхронизированный (принтер)» никогда не заканчивается, т.е. блокируется Объект «принтер» никогда не освобождается даже после вызова NotifyAll().

попробуйте добавить приведенный ниже код в основной метод в начале, чтобы протестировать описанный выше сценарий.

synchronized (mainThread) {
            synchronized (printer){
                System.out.println("Before");
                mainThread.wait();
                System.out.println("After");
            }

РЕШЕНИЕ. Закройте блок «синхронизированный (принтер)» сразу после «printer.notifyAll()», чтобы блокировка «принтера» была снята после уведомления и до получения «mainThread».

person ankur mishra    schedule 14.05.2020