Сопоставьте блокировку экземплярного и статического synchronized-метода: почему их одновременное выполнение не блокирует друг друга?
Экземплярный synchronized-метод блокирует монитор конкретного объекта, на котором он вызван. Статический synchronized-метод блокирует монитор объекта Class, соответствующего классу. Это разные мониторы, поэтому вызовы могут выполняться одновременно.
Ключевое слово synchronized появилось как высокоуровневый механизм взаимного исключения и координации потоков на основе встроенных мониторов Java. Для методов язык предоставил сокращённую запись, чтобы разработчику не приходилось явно указывать объект, используемый для блокировки.
Такой синтаксис удобен, но скрывает идентификатор монитора. Из-за этого легко ошибочно предположить, что все synchronized-методы одного класса используют единую блокировку.
Рассмотрим класс, в котором один поток вызывает синхронизированный экземплярный метод, а другой — синхронизированный статический метод. Если считать, что они защищены одной блокировкой, можно неверно оценить параллелизм и решить, что доступ к общему состоянию безопасен.
На практике экземплярный метод защищает состояние конкретного объекта, а статический — состояние, связанное с классом. Если эти области данных пересекаются, раздельные мониторы не предотвращают гонку.
Для нестатического synchronized-метода монитором является значение this. Два вызова такого метода блокируют друг друга только тогда, когда обращаются к одному экземпляру; вызовы на разных объектах используют разные мониторы.
Для статического synchronized-метода монитором является объект класса, то есть объект Class, связанный с данным классом. Поэтому экземплярный и статический методы одного класса обычно блокируют разные объекты: экземпляр класса и его объект Class.
Вызовы updateInstance() и updateGlobal() могут выполняться одновременно. Взаимное исключение действует только внутри каждой отдельной группы вызовов, использующей один и тот же монитор.
Это не означает, что статический метод всегда безопасен для общего состояния: безопасность зависит от того, какие данные он защищает и все ли обращения к ним используют тот же монитор. Если требуется единая критическая секция для экземплярных и статических операций, нужно явно выбрать общий объект блокировки и применять его последовательно.
Следует также учитывать, что синхронизация защищает не «метод вообще», а участок выполнения относительно конкретного монитора. Она обеспечивает взаимное исключение и соответствующие гарантии видимости только между потоками, синхронизирующимися на одном объекте.
В сервисе есть экземплярный метод изменения локального состояния объекта и статический метод обновления общего реестра. Разработчик предположил, что synchronized в обоих объявлениях автоматически сериализует операции, но одновременные изменения привели к неконсистентному результату в общем реестре.
Вариант с добавлением synchronized ко всем методам не решает проблему: экземплярные методы разных объектов всё равно используют разные мониторы, а статические методы используют монитор Class. Вариант с блокировкой на строковом литерале или публичном объекте опасен из-за возможного внешнего доступа к тому же монитору.
Надёжнее выделить приватный объект блокировки для конкретного общего состояния и синхронизировать все операции над этим состоянием на нём. Если состояние действительно глобальное и требуется одна блокировка на класс, допустимо использовать единый приватный статический монитор; это делает намерение явным и не раскрывает блокировку внешнему коду.
Нет. Экземплярный метод блокирует this, а статическое поле и статический synchronized-метод обычно защищаются монитором объекта Class. Если доступ к статическому полю выполняется через разные экземпляры, экземплярные методы будут использовать разные мониторы и не обеспечат взаимное исключение.
Нет. Каждый объект имеет собственный монитор, поэтому вызовы на разных экземплярах могут выполняться параллельно. Они будут взаимно исключаться только при вызове на одном и том же объекте либо если оба участка явно синхронизируются на общем мониторе.
Нет. Важно не только наличие блокировки, но и идентичность объекта, на котором она устанавливается. Если разные участки используют разные объекты, взаимного исключения не будет; если используется объект, доступный внешнему коду, посторонний код может непреднамеренно задержать или нарушить работу. Поэтому обычно выбирают приватный, неизменяемый объект блокировки и используют его во всех связанных критических секциях.