Сохраняется ли модификатор synchronized при переопределении экземплярного метода в классе-наследнике?
Нет. synchronized не наследуется как обязательное свойство переопределяющего метода: класс-наследник может объявить переопределённый метод без синхронизации, и тогда вызов через ссылку базового типа всё равно динамически попадёт в метод наследника, но блокировка выполнена не будет.
В этом примере вызов process у объекта Child не синхронизирован, несмотря на synchronized в Base.
В Java синхронизация является свойством конкретного объявления метода, а не частью механизма полиморфного контракта. Это позволяет наследнику самостоятельно выбирать стратегию доступа к общему состоянию, но одновременно означает, что потокобезопасность не гарантируется автоматически при переопределении.
Такой подход отделяет выбор реализации метода от механизма блокировок. Язык не навязывает наследнику сохранение всех поведенческих деталей реализации базового класса.
Если базовый класс защищает состояние с помощью synchronized, разработчик может ошибочно считать, что защита сохранится во всех подклассах. На практике переопределённый метод может снять синхронизацию, что приведёт к гонкам данных при конкурентных вызовах.
Особенно опасен вызов через ссылку базового типа: тип ссылки определяет доступность метода, но фактическая реализация выбирается по объекту. Поэтому ссылка типа Base не гарантирует, что будет использована синхронизированная реализация Base.
При вызове экземплярного метода сначала определяется доступный метод по статическому типу ссылки, а затем для виртуального вызова выбирается переопределение фактического класса объекта. Модификатор synchronized проверяется у реально выбранного объявления метода.
Для обычного экземплярного synchronized-метода блокируется монитор текущего объекта, то есть this. Если наследник также объявляет метод как synchronized, он использует тот же монитор объекта, но это происходит потому, что модификатор указан в объявлении наследника, а не потому, что он автоматически унаследован.
Переопределяющий метод может как добавить, так и убрать synchronized. Компилятор не считает снятие синхронизации нарушением правил переопределения, поскольку synchronized не входит в сигнатуру метода и не является ограничением совместимости переопределения.
Для сохранения гарантии есть несколько подходов:
final-методом, который удерживает контроль над синхронизацией и вызывает расширяемый шаг.У статических методов поведение отличается: они не переопределяются, а скрываются. Для static synchronized блокируется объект Class, поэтому при скрытии метода базового класса и класса-наследника могут использоваться разные мониторы.
Базовый класс хранит общий кэш и объявляет синхронизированный метод обновления. Команда создаёт наследника, который переопределяет метод для оптимизации и удаляет synchronized, считая, что синхронизация уже задана в базовом классе. При нагрузке несколько потоков начинают одновременно изменять состояние кэша.
Вариант с добавлением synchronized в каждый наследник прост, но требует дисциплины и легко нарушается новым подклассом. Внешняя блокировка гибче, однако нужно строго соблюдать единый монитор, иначе защита будет неполной.
Наиболее надёжный вариант для инварианта базового класса — сделать управляющий метод final и оставить наследнику отдельный несинхронизированный шаг, вызываемый из защищённой секции. Это не всегда подходит для расширяемой архитектуры, но предотвращает случайное снятие обязательной блокировки. В результате правило синхронизации находится в одном месте и не зависит от решений подклассов.
Что произойдёт при вызове переопределённого метода через ссылку базового типа, если версия базового метода synchronized, а версия наследника — нет?
Будет вызвана версия наследника благодаря динамическому полиморфизму, и монитор объекта автоматически захвачен не будет. Статический тип ссылки не возвращает выполнение в базовый метод и не восстанавливает его модификаторы.
Можно ли считать добавление synchronized в переопределяющем методе усилением контракта базового класса?
На уровне языка это не проверяется как обязательное усиление контракта. Компилятор разрешает добавить модификатор, но клиентский код не может полагаться на него через общий тип без документированного поведенческого соглашения. Кроме того, синхронизация метода защищает только выбранный монитор и не гарантирует корректность операций, выполняемых вне этого метода.
Как изменится блокировка, если переопределяемый метод является static synchronized?
Статический метод не участвует в динамическом полиморфизме: выбор зависит от типа, через который обращаются к методу. При скрытии метода базовый класс синхронизируется на мониторе объекта Class базового класса, а наследник — на мониторе Class наследника. Поэтому такие методы не используют общий монитор экземпляра и могут выполняться параллельно относительно друг друга.