При исключении внутри synchronized-блока кто и в какой момент освобождает монитор?
Монитор освобождается автоматически при выходе потока из synchronized-блока, в том числе из-за исключения. Освобождение происходит до передачи исключения обработчику за пределами блока, поэтому другие потоки смогут захватить монитор.
Это гарантируется самой семантикой synchronized, а не наличием catch или finally в пользовательском коде.
Мониторы в Java проектировались как механизм структурированной синхронизации: поток входит в критическую секцию, выполняет работу и гарантированно покидает её с восстановлением состояния блокировки. Такой подход решает проблему забытых разблокировок, особенно при досрочном выходе через return или исключение.
В отличие от ручного управления блокировкой, synchronized связывает захват и освобождение монитора с границами блока или метода. Это делает базовый сценарий безопаснее, хотя не устраняет логические ошибки в проектировании критических секций.
Если поток захватил монитор и исключение прервало выполнение критической секции, ручной механизм разблокировки может не выполниться. Остальные потоки тогда будут бесконечно ждать монитор или дождутся его только после внешнего вмешательства, если используемый механизм вообще поддерживает такую возможность.
Для synchronized это не происходит: монитор освобождается даже при непроверяемом исключении или Error. Однако действия, выполненные до исключения, не откатываются автоматически, поэтому защищённое состояние может остаться частично изменённым.
При нормальном завершении блока, return, break, continue или исключении JVM выполняет выход из мониторной секции и уменьшает счётчик входов текущего потока в этот монитор. Если счётчик становится равен нулю, монитор становится доступен другим потокам.
Это особенно важно для повторного входа: один поток может несколько раз войти в монитор. Исключение из внутреннего уровня уменьшает счётчик только на один уровень; полное освобождение произойдёт после выхода из всех вложенных synchronized-секций.
Упрощённо семантику можно представить так:
JVM обеспечивает эквивалентное по смыслу освобождение монитора при любом выходе:
Это не означает, что монитор будет захвачен всегда: если вычисление объекта синхронизации завершилось ошибкой, например из-за null, вход в монитор не произойдёт. Также автоматическое освобождение не делает составную операцию атомарной с точки зрения бизнес-логики и не возвращает состояние к прежнему значению.
У ReentrantLock похожую гарантию нужно явно обеспечивать через try/finally после успешного lock(). Поэтому synchronized проще для базовой взаимной блокировки, а ReentrantLock предоставляет дополнительные возможности: прерываемое ожидание, попытку захвата и несколько условий ожидания.
Поток обновляет состояние заказа под монитором, а затем вызывает компонент, который иногда выбрасывает исключение. Вариант с synchronized не оставит монитор занятым, но может сохранить заказ в промежуточном состоянии.
Можно уменьшить критическую секцию и выполнять потенциально ошибочный внешний вызов за пределами монитора. Это снижает риск удержания блокировки и взаимных зависимостей, но требует заранее сохранить согласованное внутреннее состояние.
Другой вариант — ReentrantLock с явным try/finally. Он подходит, если нужны tryLock() или прерываемое ожидание, но увеличивает вероятность ошибки в обслуживающем коде. Для простой защиты короткого участка выбран бы synchronized, а согласованность данных обеспечил бы отдельной проверкой переходов состояния.
Освобождается ли монитор, если исключение перехватывается внутри synchronized-блока?
Нет, пока выполнение остаётся внутри блока, монитор продолжает принадлежать потоку. Если обработчик исключения находится внутри критической секции, другие потоки не смогут войти до выхода из всего synchronized-блока.
Откатываются ли изменения полей после автоматического освобождения монитора?
Нет. synchronized управляет взаимным исключением и связанными с ним гарантиями видимости, но не является транзакцией. После исключения часть полей может быть обновлена, поэтому объект должен либо поддерживать промежуточное состояние, либо использовать явное восстановление или подготовку нового согласованного состояния до публикации.
Что произойдёт, если внутри synchronized-блока вызвать другой synchronized-метод того же объекта, а затем получить исключение?
Монитор является реентерабельным: тот же поток сможет войти повторно, увеличив внутренний счётчик входов. Исключение из внутреннего метода уменьшит счётчик только при выходе из него, а монитор окончательно освободится лишь после выхода из внешней секции. Если исключение не перехвачено, оно продолжит распространяться наружу, но JVM всё равно корректно снимет все уровни владения по мере размотки стека.