Запутанное использование синхронизированных в Java: шаблон или анти-шаблон? - PullRequest
9 голосов
/ 30 сентября 2011

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

synchronized (this) {
    this.notify();
}

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

Ответы [ 6 ]

10 голосов
/ 30 сентября 2011

Это, конечно, не бессмысленно, у вас может быть другой поток, имеющий ссылку на объект, содержащий приведенный выше код, делающий

synchronized(foo) {
    foo.wait();
}

для того, чтобы проснуться, когда что-то происходит. Хотя во многих случаях рекомендуется синхронизировать объект внутренней / частной блокировки вместо this.

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

3 голосов
/ 30 сентября 2011

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

Когда вы используете ожидание и уведомление, вы должны использовать условную переменную, см. это руководство по Oracle :

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

Не следует предполагать, что вы получили уведомление только потому, что поток завершил вызов из Object # wait по нескольким причинам:

  • При вызовеверсия ожидания, которая принимает значение тайм-аута. Нет способа узнать, закончилось ли ожидание из-за получения уведомления или из-за истечения времени ожидания.

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

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

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

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

2 голосов
/ 30 сентября 2011

Это прекрасно. Согласно документации Java 6 Object#notify() API :

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

1 голос
/ 30 сентября 2011

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

wait (), notify () и notifyAll () внутри синхронизированного оператора

1 голос
/ 30 сентября 2011

В документации API Java для Object.notify () указано, что метод "должен вызываться только потоком, который является владельцем монитора этого объекта".Таким образом, использование может быть законным в зависимости от окружающего контекста.

1 голос
/ 30 сентября 2011

Обычно это не анти-паттерн, если вы все еще хотите использовать встроенные блокировки.Некоторые могут расценивать это как анти-паттерн, так как новые явные блокировки из java.util.concurrent более точные.

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

...