Куда передаётся неперехваченное исключение после завершения поиска обработчика в стеке вызовов потока?
После того как поток не нашёл подходящий catch, JVM передаёт исключение его UncaughtExceptionHandler. Сначала используется обработчик, назначенный конкретному потоку; если его нет, применяется обработчик группы потока, а затем глобальный обработчик по умолчанию. Этот механизм предназначен для фиксации аварийного завершения, а не для продолжения работы потока.
У потока нет вызывающего кода в другом потоке, которому можно было бы передать исключение через обычный стек вызовов. Поэтому Java отделяет поиск локального catch от последнего этапа обработки — уведомления специального обработчика о необработанном исключении.
Такой подход позволяет централизованно регистрировать аварии потоков, не добавляя одинаковый внешний try-catch в каждый запускаемый поток.
Если исключение осталось необработанным, поток завершается. UncaughtExceptionHandler не возвращает управление в поток и не даёт продолжить выполнение после точки сбоя.
Неверное предположение особенно опасно в серверных приложениях: разработчик может ожидать, что глобальный обработчик перехватит любую ошибку фоновой задачи. Однако некоторые абстракции, например FutureTask, сами перехватывают исключение и сохраняют его внутри объекта результата.
Поиск выполняется по стеку вызовов текущего потока. Если подходящий catch не найден, JVM вызывает метод uncaughtException у обработчика потока, полученного через Thread.getUncaughtExceptionHandler().
Приоритет имеет обработчик, установленный непосредственно на объекте Thread. Если он не задан, используется группа потока; в стандартной конфигурации она может передать событие глобальному обработчику, установленному через Thread.setDefaultUncaughtExceptionHandler. Если подходящего пользовательского обработчика нет, применяется стандартное поведение среды, обычно связанное с выводом информации об исключении.
Обработчик получает сам объект Throwable, поэтому он может анализировать как Exception, так и Error. Но это не отменяет завершение потока и не заменяет корректное управление жизненным циклом ресурсов.
В примере локального обработчика у worker нет, поэтому используется обработчик по умолчанию. Вызов join() нужен только для ожидания завершения потока; он не перехватывает исключение потока.
Практический компромисс таков: глобальный обработчик удобен для последнего уровня логирования и метрик, но не должен быть основным способом восстановления. Восстановление нужно проектировать на уровне задачи или управляющего компонента, где известны её контекст и допустимая стратегия повторного запуска.
В сервисе фоновые задачи запускаются через ExecutorService. Для задач, переданных через execute, необработанное исключение обычно выходит из рабочего запуска и может привести к вызову обработчика необработанных исключений для потока.
Для задач, переданных через submit, исключение перехватывает FutureTask и сохраняет в Future. Поэтому глобальный UncaughtExceptionHandler может не сработать: ошибку необходимо получить через Future.get, где она будет представлена как причина ExecutionException.
Варианты решения:
submit можно не увидеть;try-catch — надёжно для контекста, но приводит к дублированию;Оптимален третий вариант: декоратор регистрирует ошибки с идентификатором задачи, а UncaughtExceptionHandler остаётся последним защитным уровнем для ошибок, вышедших за границы обычной обработки.
UncaughtExceptionHandler исключение, возникшее в другом потоке?Нет. Обработчик относится к конкретному потоку и вызывается только для необработанного исключения этого потока. try-catch одного потока также не распространяется на стек вызовов другого потока; для передачи ошибки нужен явный механизм, например Future, очередь событий или callback.
UncaughtExceptionHandler выбросит исключение?Это не создаёт новый рабочий цикл обработки. Исходный поток уже завершается из-за первоначального необработанного исключения, а исключение из обработчика не следует рассматривать как способ восстановления. Поэтому обработчик должен быть максимально надёжным: избегать сложной логики, блокировок и операций, которые сами могут завершиться ошибкой.
UncaughtExceptionHandler продолжить выполнение метода после сбоя?Нет. Он вызывается после того, как обычный механизм поиска catch исчерпан. Обработчик может записать диагностику, отправить сигнал мониторингу или инициировать перезапуск внешнего компонента, но выполнение прерванного метода и потока не возобновляется.