Сопоставьте блокировку экземплярного и статического synchronized метода: почему их одновременное выполнение...

Сопоставьте блокировку экземплярного и статического synchronized-метода: почему их одновременное выполнение не блокирует друг друга?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Экземплярный synchronized-метод блокирует монитор конкретного объекта, на котором он вызван. Статический synchronized-метод блокирует монитор объекта Class, соответствующего классу. Это разные мониторы, поэтому вызовы могут выполняться одновременно.

Исторический контекст

Ключевое слово synchronized появилось как высокоуровневый механизм взаимного исключения и координации потоков на основе встроенных мониторов Java. Для методов язык предоставил сокращённую запись, чтобы разработчику не приходилось явно указывать объект, используемый для блокировки.

Такой синтаксис удобен, но скрывает идентификатор монитора. Из-за этого легко ошибочно предположить, что все synchronized-методы одного класса используют единую блокировку.

Постановка проблемы

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

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

Подробное решение

Для нестатического synchronized-метода монитором является значение this. Два вызова такого метода блокируют друг друга только тогда, когда обращаются к одному экземпляру; вызовы на разных объектах используют разные мониторы.

Для статического synchronized-метода монитором является объект класса, то есть объект Class, связанный с данным классом. Поэтому экземплярный и статический методы одного класса обычно блокируют разные объекты: экземпляр класса и его объект Class.

class Registry { private int instanceValue; private static int globalValue; synchronized void updateInstance() { instanceValue++; } static synchronized void updateGlobal() { globalValue++; } }

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

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

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

Ситуация из практики

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

Вариант с добавлением synchronized ко всем методам не решает проблему: экземплярные методы разных объектов всё равно используют разные мониторы, а статические методы используют монитор Class. Вариант с блокировкой на строковом литерале или публичном объекте опасен из-за возможного внешнего доступа к тому же монитору.

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

Что кандидаты часто упускают

  1. Достаточно ли сделать оба метода synchronized, если экземплярный метод изменяет статическое поле?

Нет. Экземплярный метод блокирует this, а статическое поле и статический synchronized-метод обычно защищаются монитором объекта Class. Если доступ к статическому полю выполняется через разные экземпляры, экземплярные методы будут использовать разные мониторы и не обеспечат взаимное исключение.

  1. Будут ли два вызова экземплярного synchronized-метода на разных объектах блокировать друг друга?

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

  1. Можно ли заменить synchronized-метод synchronized-блоком на любом доступном объекте без изменения поведения?

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