Объясните механизм повторного входа одного потока в уже захваченный им монитор synchronized.
Мониторы, используемые synchronized, являются реентерабельными: поток, уже владеющий монитором, может повторно захватить тот же монитор без взаимной блокировки. Каждый повторный вход увеличивает внутренний счётчик удержаний, а выход уменьшает его; монитор становится доступен другим потокам только после полного выхода.
Реентерабельность относится только к тому же потоку и тому же объекту-монитору. Она не предотвращает блокировку, если поток захватывает другие мониторы в конфликтующем порядке.
Синхронизация на встроенных мониторах Java предназначена для защиты критических секций и координации доступа к общему состоянию. Без реентерабельности вызов одного синхронизированного метода из другого синхронизированного метода того же объекта приводил бы к самоблокировке.
Реентерабельность позволяет безопасно строить вложенные вызовы: общий внутренний метод может быть синхронизирован независимо от того, вызван ли он напрямую или из уже защищённой операции.
Поток может войти в синхронизированный метод, а затем вызвать другой синхронизированный метод того же объекта. Оба метода используют один монитор — обычно монитор this.
Если бы повторный захват уже принадлежащего потоку монитора блокировал этот же поток, выполнение остановилось бы навсегда. При этом другие потоки всё равно не смогли бы продолжить работу, потому что монитор оставался бы захваченным.
При входе в synchronized поток захватывает монитор объекта. Если монитор уже принадлежит этому же потоку, JVM не передаёт поток в состояние ожидания, а увеличивает счётчик повторных захватов.
Каждый успешный вход должен иметь соответствующий выход. Поэтому после выхода из внутреннего синхронизированного участка монитор всё ещё удерживается внешним участком. Только последний выход, соответствующий исходному захвату, освобождает монитор для других потоков.
Например:
При вызове transfer() поток захватывает монитор объекта Account. Вызов validate() повторно захватывает тот же монитор, выполняется без блокировки, а после возврата управление остаётся внутри внешней критической секции.
Реентерабельность не означает, что синхронизация становится безопасной автоматически. Вызов внешнего кода, например listener или callback, под удерживаемым монитором может привести к длительным блокировкам, взаимной блокировке с другим монитором или неожиданной конкуренции за тот же объект.
Важно также различать монитор объекта и монитор класса. Синхронизированный экземплярный метод использует конкретный объект, а static synchronized-метод — объект Class. Повторный вход безопасен только при совпадении владельца и самого монитора.
Компромисс встроенного монитора — простой синтаксис и автоматическое освобождение при выходе из блока, включая выход из-за исключения. Ограничение — нельзя напрямую управлять тайм-аутом захвата или прерывать ожидание так, как это позволяет ReentrantLock.
Сервис хранит состояние и уведомляет подписчиков об изменении. Один из подписчиков при получении уведомления вызывает обратно метод сервиса, который читает это состояние. Если уведомление выполняется внутри synchronized-метода, обратный вызов тем же потоком не зависнет именно благодаря реентерабельности.
Рассматриваются варианты:
synchronized на ReentrantLock — появляются тайм-ауты и дополнительные возможности, но увеличивается сложность и риск неправильного освобождения;Обычно выбирают третий вариант: критическая секция остаётся короткой, а внешний код не вызывается под внутренним монитором. Реентерабельность всё равно полезна для внутренних вызовов, но не должна быть основанием для удержания блокировки во время произвольных callback-вызовов.
Увеличивает ли повторный вход число разрешений для других потоков?
Нет. Повторный вход увеличивает счётчик удержаний, но не создаёт дополнительные права доступа для других потоков. Другой поток сможет захватить монитор только после того, как текущий поток выйдет из всех вложенных синхронизированных участков, использующих этот монитор.
Работает ли реентерабельность при захвате двух разных объектов?
Нет, она действует только для того же монитора. Если поток удерживает монитор объекта A и пытается захватить монитор объекта B, он может заблокироваться на B. Одновременно другой поток может удерживать B и ждать A — это классический сценарий взаимной блокировки.
Устраняет ли реентерабельность проблемы видимости данных между потоками?
Нет, она решает только повторный захват монитора тем же потоком. Вход в synchronized после выхода другого потока из синхронизированного участка участвует в отношениях happens-before, поэтому корректно используется для публикации изменений, но сам факт реентерабельности не делает несинхронизированные обращения к общим данным безопасными.