Подтвердить что ты не робот

Почему wait/notify/notify Все методы не синхронизируются в java?

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

В случае, если они объявлены как синхронизированные, он автоматически включил доступ к монитору.

4b9b3361

Ответ 1

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

Вот пример, речь идет о простейшей реализации очереди, которую вы можете иметь в Java:

public class MyQueue<T> {

    private List<T> list = new ArrayList<T>();

    public T take() throws InterruptedException {
        synchronized(list) {
            while (list.size() == 0) {
                list.wait();
            }
            return list.remove(0);
        }
    }

    public void put(T object) {
        synchronized(list) {
            list.add(object);
            list.notify();
        }
    }
}

Итак, у вас могут быть потоки производителей, которые добавляют вещи в очередь и потоки потребителей, которые вынимают вещи. Когда поток идет, чтобы получить что-то из очереди, ему нужно проверить внутри синхронизированного блока, что есть что-то в списке, и после того, как он был уведомлен, ему необходимо повторно зафиксировать блокировку и убедиться, что в списке все еще что-то (потому что некоторые другая потребительская нить могла бы вмешаться и схватить его). Также существует феномен "побочного пробуждения": вы не можете рассчитывать на то, что проснулись в качестве достаточного доказательства того, что что-то произошло, вам нужно проверить, что бы вы ни ожидали for действительно истинно, и это необходимо сделать в синхронизированном блоке.

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

Ответ 2

Хороший вопрос. Комментарии в JDK7 Object implementation прояснили это, я думаю (основное внимание):

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

...

Затем поток T удаляется из набора ожидания для этого обычный способ с другими потоками для права синхронизации на объект; как только он получит контроль над объектом, все его требования синхронизации к объекту восстанавливаются в статус-кво ante - то есть к ситуации со временем, когда waitметод был вызван. Тема T, затем возвращается из вызов метода wait. Таким образом, по возвращении из wait, состояние синхронизации объекта и потока T точно так же, как и когда метод waitвызывается.

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

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

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

Или, возможно, это было просто сделано, чтобы избежать появления логического парадокса "если wait() и notify() оба синхронизированы, а wait() не возвращается до вызова notify(), как можно или когда-либо успешно использовать?".

Это мои мысли, так или иначе.

Ответ 3

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

Findbugs даже имеет предупреждение для этого, которое оно вызывает Голый уведомить".

Ответ 4

Среди всех кодов ошибок, которые я прочитал и написал, все они используют wait/notify в большем блоке синхронизации, включающем чтение/запись других условий

synchronized(lock)
    update condition
    lock.notify()

synchronized(lock)
    while( condition not met)
        lock.wait()

Если wait/notify сами synchronized, никакого вреда не будет сделано для всех правильных кодов (может быть небольшое снижение производительности); он не будет полезен ни ко всем правильным кодам.

Однако это позволило бы и поощрять гораздо более неправильные коды.

Ответ 5

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

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

Ответ 6

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

Модель ожидания-уведомления или взаимного сотрудничества обычно используется в сценарии производителя-потребителя, где один поток создает события, которые потребляются другим потоком. Хорошо написанные реализации будут стремиться избегать сценариев, когда потребитель голодает, или производитель переполняет потребителя слишком большим количеством событий. Чтобы этого избежать, вы должны использовать протокол wait-notify, в котором

  • потребитель wait для производителя для создания события.
  • производитель производит событие и notifies потребитель, а затем обычно переходит в спящий режим, пока пользователь не будет notified.
  • Когда потребитель уведомляется о событии, он просыпается, обрабатывает событие и notifies производителя, что он завершил обработку события.

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

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

Вы можете больше узнать о наборах ожидания и наборах записей мониторов в книге Билла Веннера - Внутри виртуальной машины Java.