Java: notify() vs. notifyAll() снова и снова

Если один Googles для "разницы между notify() и notifyAll()", тогда появится много объяснений (не считая абзацев javadoc). Все это сводится к тому, что количество ожидающих потоков пробуждается: один в notify() и все в notifyAll().

Однако (если я правильно понимаю разницу между этими методами), для дальнейшего наблюдения за монитором всегда выбирается только один поток; в первом случае - тот, который выбран виртуальной машиной, во втором случае - тот, который выбран планировщиком системных потоков. Точные процедуры выбора для обоих из них (в общем случае) не известны программисту.

Какая полезная разница между notify() и notifyAll(), то? Я что-то пропустил?

Ответы

Ответ 1

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

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

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

Ответ 2

Ясно, что notify просыпает (любой) один поток в наборе ожидания, notifyAll просыпает все потоки в ожидании. Следующее обсуждение должно устранить любые сомнения. notifyAll следует использовать большую часть времени. Если вы не уверены, что использовать, используйте notifyAll. Пожалуйста, смотрите объяснение, которое следует ниже.

Читайте очень внимательно и понимайте. Если у вас есть какие-либо вопросы, пришлите мне электронное письмо.

Посмотрите на производителя/потребителя (предположение является классом ProducerConsumer с двумя методами). ЭТО БРОКЕН (потому что он использует notify) - да, он МОЖЕТ работать - даже большую часть времени, но может также вызвать тупик - мы увидим, почему:

public synchronized void put(Object o) {
    while (buf.size()==MAX_SIZE) {
        wait(); // called if the buffer is full (try/catch removed for brevity)
    }
    buf.add(o);
    notify(); // called in case there are any getters or putters waiting
}

public synchronized Object get() {
    // Y: this is where C2 tries to acquire the lock (i.e. at the beginning of the method)
    while (buf.size()==0) {
        wait(); // called if the buffer is empty (try/catch removed for brevity)
        // X: this is where C1 tries to re-acquire the lock (see below)
    }
    Object o = buf.remove(0);
    notify(); // called if there are any getters or putters waiting
    return o;
}

Во-первых,

Зачем нам нужен цикл while, окружающий ожидание?

Нам нужен цикл while, если мы получим следующую ситуацию:

Потребитель 1 (C1) вводит синхронизированный блок, и буфер пуст, поэтому C1 помещается в набор ожидания (через вызов wait). Потребитель 2 (C2) собирается ввести синхронизированный метод (в точке Y выше), но Producer P1 помещает объект в буфер и затем вызывает notify. Единственный ожидающий поток - C1, поэтому он проснулся и теперь пытается повторно захватить блокировку объекта в точке X (см. Выше).

Теперь C1 и C2 пытаются получить блокировку синхронизации. Один из них (недетерминированно) выбирается и входит в метод, другой блокируется (не ждет - но блокируется, пытаясь получить блокировку метода). Пусть, скажем, C2 сначала получает блокировку. C1 все еще блокирует (пытается получить блокировку на X). C2 завершает метод и освобождает блокировку. Теперь C1 получает блокировку. Угадайте, что, повезло, у нас есть цикл while, потому что C1 выполняет проверку цикла (guard) и не позволяет удалить несуществующий элемент из буфера (C2 уже получил его!). Если бы у нас не было while, мы получили бы IndexArrayOutOfBoundsException, поскольку C1 пытается удалить первый элемент из буфера!

сейчас,

Хорошо, теперь зачем нам уведомлять?

В приведенном выше примере производителя/потребителя мы видим, что мы можем уйти с notify. Это похоже на то, потому что мы можем доказать, что охранники в цикле ожидания для производителя и потребителя являются взаимоисключающими. То есть, похоже, что мы не можем ожидать поток в методе put, а также метод get, потому что для того, чтобы быть истинным, тогда должно быть верно следующее:

buf.size() == 0 AND buf.size() == MAX_SIZE (предположим, что MAX_SIZE не 0)

ОДНАКО, это недостаточно, нам нужно использовать notifyAll. Давайте посмотрим, почему...

Предположим, что у нас есть буфер размером 1 (чтобы упростить пример). Следующие шаги приводят нас к взаимоблокировке. Обратите внимание, что ANYTIME поток просчитывается с уведомлением, он может быть недетерминированно выбран JVM - это может быть вызвано любой ожидающий поток. Также обратите внимание, что, когда несколько потоков блокируются при входе в метод (т.е. Пытается получить блокировку), порядок получения может быть недетерминированным. Помните также, что поток может быть только одним из методов в любой момент времени - синхронизированные методы позволяют выполнять только один поток (т.е. Удерживать блокировку) любых (синхронизированных) методов в классе. Если происходит следующая последовательность событий - результаты взаимоблокировки:

ШАГ 1:
- P1 помещает 1 char в буфер

ШАГ 2:
- попытки P2 put - проверяет цикл ожидания - уже a char - ждет

ШАГ 3:
- попытки P3 put - проверяет цикл ожидания - уже a char - ждет

ШАГ 4:
- C1 пытается получить 1 char
- C2 пытается получить 1 char - блоки при входе в метод get
- C3 пытается получить 1 char - блоки при входе в метод get

ШАГ 5:
- C1 выполняет метод get - получает char, вызывает notify, выдает метод
- notify просыпается P2
- НО, C2 вводит метод до того, как P2 может (P2 должен восстановить блокировку), поэтому P2 блокируется при входе в метод put
- C2 проверяет цикл ожидания, больше символов в буфере, поэтому ждет
- C3 вводит метод после C2, но до P2 проверяет цикл ожидания, больше символов в буфере, поэтому ждет

ШАГ 6:
- СЕЙЧАС: ждут P3, C2 и C3!
- Наконец P2 получает блокировку, помещает char в буфер, вызывает уведомления, выдает метод

ШАГ 7:
- уведомление P2 пробуждает P3 (помните, что любой поток можно разбудить)
- P3 проверяет условие ожидания цикла, в буфере уже есть char, поэтому ждет.
- НЕТ БОЛЬШЕ НИТЕЙ, ЧТОБЫ ЗВОНИТЬ УВЕДОМЛЕНИЕ, И ТРЕХ НИТЕЙ ПОСТОЯННО ПОДОЗРЕНО!

РЕШЕНИЕ: Замените notify на notifyAll в коде производителя/потребителя (см. выше).

Ответ 3

Полезные различия:

  • Используйте notify(), если все ваши ожидающие потоки взаимозаменяемы (порядок, который они просыпают, не имеет значения), или если у вас только один ожидающий поток. Общим примером является пул потоков, используемый для выполнения заданий из очереди - при добавлении задания один из потоков уведомляется о пробуждении, выполняет следующее задание и возвращается в режим сна.

  • Используйте notifyAll() для других случаев, когда потоки ожидания могут иметь разные цели и должны работать одновременно. Примером может служить операция обслуживания на совместно используемом ресурсе, где несколько потоков ждут завершения операции перед доступом к ресурсу.

Ответ 4

Я думаю, это зависит от того, как создаются и потребляются ресурсы. Если сразу появляется 5 рабочих объектов, и у вас есть 5 потребительских объектов, имеет смысл разбудить все потоки, используя notifyAll(), чтобы каждый из них мог обрабатывать 1 рабочий объект.

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

Я нашел отличное объяснение здесь. Короче говоря:

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

Ответ 5

Обратите внимание, что с утилитами concurrency у вас также есть выбор между signal() и signalAll(), поскольку эти методы называются там. Поэтому вопрос остается в силе даже при java.util.concurrent.

Дуг Ли поднимает интересный момент в своей знаменитой книге: если a notify() и Thread.interrupt() происходят одновременно, уведомление может фактически потеряться. Если это может произойти и имеет серьезные последствия, notifyAll() является более безопасным выбором, даже если вы платите цену накладных расходов (большую часть времени пробуждаете слишком много потоков).

Ответ 6

От Джошуа Блоха, самого Гуру Java в Эффективной Java-версии 2-го издания:

"Пункт 69: Предпочитают concurrency утилиты ждать и уведомлять".

Ответ 7

Вот пример. Запустить его. Затем измените одно из notifyAll(), чтобы уведомить() и посмотреть, что произойдет.

Класс ProducerConsumerExample

public class ProducerConsumerExample {

    private static boolean Even = true;
    private static boolean Odd = false;

    public static void main(String[] args) {
        Dropbox dropbox = new Dropbox();
        (new Thread(new Consumer(Even, dropbox))).start();
        (new Thread(new Consumer(Odd, dropbox))).start();
        (new Thread(new Producer(dropbox))).start();
    }
}

Класс Dropbox

public class Dropbox {

    private int number;
    private boolean empty = true;
    private boolean evenNumber = false;

    public synchronized int take(final boolean even) {
        while (empty || evenNumber != even) {
            try {
                System.out.format("%s is waiting ... %n", even ? "Even" : "Odd");
                wait();
            } catch (InterruptedException e) { }
        }
        System.out.format("%s took %d.%n", even ? "Even" : "Odd", number);
        empty = true;
        notifyAll();

        return number;
    }

    public synchronized void put(int number) {
        while (!empty) {
            try {
                System.out.println("Producer is waiting ...");
                wait();
            } catch (InterruptedException e) { }
        }
        this.number = number;
        evenNumber = number % 2 == 0;
        System.out.format("Producer put %d.%n", number);
        empty = false;
        notifyAll();
    }
}

Класс пользователя

import java.util.Random;

public class Consumer implements Runnable {

    private final Dropbox dropbox;
    private final boolean even;

    public Consumer(boolean even, Dropbox dropbox) {
        this.even = even;
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            dropbox.take(even);
            try {
                Thread.sleep(random.nextInt(100));
            } catch (InterruptedException e) { }
        }
    }
}

Класс производителя

import java.util.Random;

public class Producer implements Runnable {

    private Dropbox dropbox;

    public Producer(Dropbox dropbox) {
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            int number = random.nextInt(10);
            try {
                Thread.sleep(random.nextInt(100));
                dropbox.put(number);
            } catch (InterruptedException e) { }
        }
    }
}

Ответ 8

Краткое описание:

Всегда предпочитайте notifyAll() поверх notify(), если у вас нет широкомасштабного параллельного приложения, в котором большое количество потоков выполняет одно и то же.

Объяснение:

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

источник: https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

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

Таким образом, если у вас нет приложения, где огромное количество потоков выполняет одно и то же одновременно, предпочитайте notifyAll() поверх notify(). Зачем? Потому что, поскольку другие пользователи уже ответили на этом форуме, notify()

просыпает один поток, ожидающий этого монитора объекта. [...] выбор произволен и происходит по усмотрению реализация.

source: Java SE8 API (https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--)

Представьте, что у вас есть потребительское приложение производителя, где потребители готовы (т.е. wait() ing) потреблять, производители готовы (т.е. wait() ing), чтобы произвести и очередь предметов (для производства/потребления) пуста. В этом случае notify() может пробудить только потребителей и никогда производителей, потому что выбор, который пробуждается, произволен. Потребительский цикл производителя не будет иметь никакого прогресса, хотя производители и потребители готовы производить и потреблять соответственно. Вместо этого пользователь проснулся (т.е. оставив статус wait()), не вынимает элемент из очереди, потому что он пуст, и notify() s другому потребителю.

Напротив, notifyAll() пробуждает как производителей, так и потребителей. Выбор, который запланирован, зависит от планировщика. Конечно, в зависимости от реализации планировщика планировщик может также планировать только потребителей (например, если вы назначаете потоки потребителей очень высокого приоритета). Однако предположение заключается в том, что опасность планирования планировщика только для потребителей ниже, чем опасность того, что JVM только пробуждает потребителей, потому что любой разумно реализованный планировщик не принимает произвольных решений. Скорее, большинство реализаций планировщика, по крайней мере, пытаются предотвратить голод.

Ответ 9

Я очень удивлен, что никто не упомянул о позорной проблеме "потерянного пробуждения" (google it).

В принципе:

  • если у вас есть несколько потоков, ожидающих в одном и том же состоянии, и
  • несколько потоков, которые могут заставить вас перейти из состояния A в состояние B и
  • несколько потоков, которые могут заставить вас перейти из состояния B в состояние A (обычно те же потоки, что и в 1.) и
  • переход из состояния A в B должен уведомлять потоки в 1.

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

Общим примером является параллельная очередь FIFO, где: несколько экземпляров (1. и 3. выше) могут перевести вашу очередь с пустого на непустую несколько декомпонентов (2. выше) могут дождаться условия "очередь не пустая" empty → not-empty должен уведомить об опасности

Вы можете легко написать чередование операций, в которых, начиная с пустой очереди, взаимодействуют 2 enqueuers и 2 dequeuers, а 1 enqueuer будет спать.

Это проблема, сопоставимая с проблемой взаимоблокировки.

Ответ 10

Надеюсь, это уберет некоторые сомнения.

notify(): метод notify() пробуждает один поток ожидания для блокировки (первый поток, который вызвал wait() на этой блокировке).

notifyAll(): метод notifyAll() пробуждает все потоки, ожидающие блокировки; JVM выбирает один из потоков из списка потоков, ожидающих блокировку и просыпается эта нить вверх.

В случае одного потока, ожидающего блокировку, нет существенной разницы между notify() и notifyAll(). Тем не менее, когда есть несколько потоков, ожидающих блокировку, как в notify(), так и notifyAll(), точный поток пробуждается под управлением JVM, и вы не можете программно контролировать пробуждение конкретный поток.

На первый взгляд кажется, что рекомендуется просто вызвать notify(), чтобы разбудить один поток; может показаться излишним разбудить все потоки. Однако проблема с notify() заключается в том, что пробужденный поток не может быть подходящим для, который должен быть разбужен (поток может ожидать какое-то другое условие или условие все еще не выполняется для это нить и т.п). В этом случае, notify() может быть потерян, и никакой другой поток не будет просыпаться, что потенциально приведет к типу тупика (уведомление будет потеряно, а все остальные потоки ждут уведомления - навсегда).

Чтобы избежать этой проблемы, всегда лучше вызывать notifyAll(), когда существует несколько потоков, ожидающих блокировку (или более одного условия, на которых выполняется ожидание). Метод notifyAll() пробуждает все потоки, поэтому он не очень эффективен. однако эта потеря производительности незначительна в реальных приложениях.

Ответ 11

Все приведенные выше ответы верны, насколько я могу судить, поэтому я расскажу вам еще кое-что. Для производственного кода вы действительно должны использовать классы в java.util.concurrent. Их очень мало можно сделать для вас, в области concurrency в java.

Ответ 12

Этот ответ представляет собой графическое переписывание и упрощение отличного ответа xagyg, включая комментарии eran.

Зачем использовать notifyAll, даже если каждый продукт предназначен для одного пользователя?

Рассмотрим производителей и потребителей, упрощенных следующим образом.

Производитель:

while (!empty) {
   wait() // on full
}
put()
notify()

Потребитель:

while (empty) {
   wait() // on empty
}
take()
notify()

Предположим, что 2 производителя и 2 потребителя используют буфер размером 1. На следующем рисунке показан сценарий, приводящий к тупику, чего можно было бы избежать, если бы все потоки использовали notifyAll.

Каждый опознаватель помечен прорисованным потоком.

deadlock due to notify

Ответ 13

notify() просыпает один поток, а notifyAll() просыпается. Насколько я знаю, нет среднего места. Но если вы не уверены, что notify() будет делать ваши потоки, используйте notifyAll(). Работает как шарм каждый раз.

Ответ 14

notify() позволяет писать более эффективный код, чем notifyAll().

Рассмотрим следующий фрагмент кода, который выполняется из нескольких параллельных потоков:

synchronized(this) {
    while(busy) // a loop is necessary here
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notifyAll();
}

Это можно сделать более эффективным, используя notify():

synchronized(this) {
    if(busy)   // replaced the loop with a condition which is evaluated only once
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notify();
}

В случае, если у вас большое количество потоков, или если условие цикла ожидания дорого оценивается, notify() будет значительно быстрее, чем notifyAll(). Например, если у вас 1000 потоков, тогда 999 потоков будут пробуждаться и оцениваться после первого notifyAll(), затем 998, затем 997 и т.д. Напротив, с решением notify() будет пробужден только один поток.

Используйте notifyAll(), когда вам нужно выбрать, какой поток будет выполнять следующую работу:

synchronized(this) {
    while(idx != last+1)  // wait until it my turn
        wait();
}
...
synchronized(this) {
    last = idx;
    notifyAll();
}

Наконец, важно понять, что в случае notifyAll() код внутри synchronized блоков, которые были пробуждены, будет выполняться последовательно, а не сразу. Предположим, что в приведенном выше примере есть три потока, а четвертый поток вызывает notifyAll(). Все три потока будут пробуждены, но только один запустит выполнение и проверит условие цикла while. Если условие true, оно снова вызовет wait(), и только тогда второй поток начнет выполнение и проверит его условие цикла while и т.д.

Ответ 15

Здесь более простое объяснение:

Вы правы, что используете ли вы notify() или notifyAll(), непосредственный результат состоит в том, что ровно один другой поток получит монитор и начнет выполнение. (Предполагая, что некоторые потоки фактически заблокированы в wait() для этого объекта, другие несвязанные потоки не впитывают все доступные ядра и т.д.). Эффект приходит позже.

Предположим, что на этом объекте ожидают потоки A, B и C, а поток A получает монитор. Разница заключается в том, что происходит, когда A выпускает монитор. Если вы использовали notify(), то B и C по-прежнему блокируются в ожидании(): они не ждут на мониторе, они ждут уведомления. Когда A отпускает монитор, B и C все еще будут сидеть там, ожидая оповещения().

Если вы использовали notifyAll(), то B и C оба перешли в прошлое из состояния ожидания "Ожидание" и оба ждут получения монитора. Когда A отпускает монитор, B или C его приобретают (если другие нити не конкурируют за этот монитор) и начните выполнение.

Ответ 16

Взгляните на код, отправленный @xagyg.

Предположим, что два разных потока ждут двух разных условий:
первый поток ждет buf.size() != MAX_SIZE, а второй поток ждет buf.size() != 0.

Предположим, что в некоторой точке buf.size() не равно 0. JVM вызывает notify() вместо notifyAll(), а первый поток уведомляется (а не второй).

Пробуется первый поток, проверяет buf.size(), который может вернуться MAX_SIZE, и возвращается к ожиданию. Второй поток не проснулся, продолжает ждать и не вызывает get().

Ответ 17

Взято из blog в Эффективной Java:

The notifyAll method should generally be used in preference to notify. 

If notify is used, great care must be taken to ensure liveness.

Итак, я понимаю (из вышеупомянутого блога, комментарий "Yann TM" на принятый ответ и Java docs):

  • notify(): JVM пробуждает один из ожидающих потоков на этом объекте. Выбор темы производится произвольно без справедливости. То же самое можно разбудить снова и снова. Таким образом, состояние системы меняется, но никакого реального прогресса не происходит. Таким образом, создается livelock.
  • notifyAll(): JVM пробуждает все потоки, а затем все потоки расследуют блокировку этого объекта. Теперь планировщик CPU выбирает поток, который получает блокировку этого объекта. Этот процесс выбора будет намного лучше, чем выбор JVM. Таким образом, обеспечение жизнеспособности.

Ответ 18

notify() просыпает первый поток, который вызывает wait() на одном и том же объекте.

notifyAll() просыпает все потоки, которые вызывали wait() на одном и том же объекте.

Сначала будет выполняться поток с наивысшим приоритетом.

Ответ 19

Я хотел бы упомянуть то, что объяснено в Java Concurrency в Практике:

Первая точка, независимо от того, уведомляете или не уведомляете?

It will be NotifyAll, and reason is that it will save from signall hijacking.

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

Теперь, если уведомление предназначалось для потока A и JVM, уведомляющего поток B, тогда нить B проснется и увидит, что это уведомление не полезно он будет ждать снова. И Thread A никогда не узнает об этом пропущенный сигнал, и кто-то угнал его уведомление.

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

Эта проблема может быть решена с использованием объекта Condition явного блокирования Lock, предоставленного в jdk 5, поскольку он обеспечивает различное ожидание для каждого предиката условия. Здесь он будет вести себя правильно и не будет проблем с производительностью, поскольку он вызовет сигнал и убедитесь, что только один поток ожидает этого условия

Ответ 20

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

Ответ 21

notify() - выбирает случайный поток из набора ожидания объекта и помещает его в состояние BLOCKED. Остальные потоки в наборе ожиданий объекта все еще находятся в состоянии WAITING.

notifyAll() - перемещает все потоки из набора ожидания объекта в состояние BLOCKED. После использования notifyAll() в наборе ожидания общего объекта нет потоков, потому что все они теперь находятся в состоянии BLOCKED, а не в состоянии WAITING.

BLOCKED - заблокирован для блокировки. WAITING - ожидание уведомления (или блокировка для завершения соединения).

Ответ 22

Существует три состояния для потока.

  • WAIT - поток не использует какой-либо процессорный цикл
  • BLOCKED - поток заблокирован, пытаясь получить монитор. Он все еще может использовать циклы процессора
  • RUNNING - поток работает.

Теперь, когда вызывается notify(), JVM выбирает один поток и перемещает их в состояние BLOCKED и, следовательно, в состояние RUNNING, так как нет конкуренции за объект монитора.

Когда вызывается notifyAll(), JVM выбирает все потоки и перемещает все из них в состояние BLOCKED. Все эти потоки будут получать блокировку объекта в приоритетном порядке. Первый способ, который может сначала получить монитор, сможет сначала перейти в состояние RUNNING и т.д.

Ответ 23

Все ответы, в которых говорится, что notifyAll() пробуждает все потоки, а затем случайным образом выбирает поток, WRONG.

notifyAll() выбирает поток с наивысшим приоритетом, а затем позволяет им работать в соответствии с их приоритетом.

Ответ 24

Подводя итог превосходным подробным объяснениям выше и самым простым способом, о котором я могу думать, это связано с ограничениями встроенного монитора JVM, который 1) приобретается на всей единице синхронизации (блоке или объекте) и 2) не делает различий в отношении того, какое конкретное условие ожидалось/уведомлялось в/о.

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

В противоположность этому, notifyAll() позволяет всем ожидающим потокам в конечном итоге повторно приобретать блокировку и проверять их соответствующее состояние, тем самым, в конечном итоге, позволяя сделать прогресс.

Так что notify() можно безопасно использовать только в том случае, если любой ожидающий поток гарантированно позволяет сделать ход, если он будет выбран, что в целом выполняется, когда все потоки одного и того же монитора проверяют только одно и то же условие - довольно редкий случай в реальных приложениях.

Ответ 25

Когда вы вызываете wait() "object" (ожидая, что блокировка объекта будет получена), intern это освободит блокировку этого объекта и поможет другим потокам блокировать этот "объект" в этом сценарии будет существовать более 1 потока, ожидающих "ресурс/объект" (учитывая, что другие потоки также выдали ожидание на том же самом вышеописанном объекте и вниз, как будет существовать поток, который заполняет ресурс/объект и вызывает уведомление /notifyAll ),

Здесь, когда вы выдаете уведомление об одном и том же объекте (с той же/другой стороны процесса/кода), это освободит заблокированный и ожидающий одиночный поток (не все ожидающие потоки - этот выпущенный поток будет выбран по JVM Thread Scheduler и весь процесс получения блокировки на объекте такой же, как и обычный).

Если у вас есть только один поток, который будет обмениваться/работать над этим объектом, вполне нормально использовать метод notify() в вашей реализации ожидания ожидания.

, если вы находитесь в ситуации, когда более одного потока читает и записывает ресурсы/объекты на основе вашей бизнес-логики, вы должны пойти на notifyAll()

теперь я смотрю, как именно jvm идентифицирует и разбивает ожидающий поток, когда мы выдаем notify() на объект...

Ответ 26

Несмотря на некоторые твердые ответы выше, я удивлен количеством недоразумений и недоразумений, которые я прочитал. Вероятно, это доказывает мысль о том, что нужно использовать java.util.concurrent как можно больше, вместо того, чтобы пытаться написать собственный сломанный параллельный код. Вернемся к вопросу: чтобы обобщить, лучшей практикой сегодня является AVOID notify() во всех ситуациях из-за проблемы с потерянным пробуждением. Любому, кто не понимает этого, не должно быть позволено писать критически важный код concurrency. Если вас беспокоит проблема скота, один безопасный способ добиться пробуждения одной нити за раз: 1. Создайте явную очередь ожидания для ожидающих потоков; 2. Пусть каждый из очереди в очереди ждет своего предшественника; 3. Если каждый поток вызывает notifyAll(), когда это делается. Или вы можете использовать Java.util.concurrent. *, Которые уже реализовали это.

Ответ 27

Пробуждение всех здесь не имеет большого значения. wait notify и notifyall, все они помещаются после владения объектным монитором. Если поток находится на стадии ожидания и вызывается уведомление, этот поток будет занимать блокировку, и ни один другой поток в этой точке не может занять эту блокировку. Таким образом, одновременный доступ не может иметь место вообще. Насколько я знаю, любой вызов ждать уведомления и notifyall может быть сделан только после того, как он заблокировал объект. Исправьте меня, если я ошибаюсь.