Что происходит, когда в потоке выбрасывается исключение?
Поведение зависит от того, перехвачено ли исключение внутри потока.
Если исключение перехвачено (caught)
Поток продолжает работу – ничего особенного не происходит. Стандартная обработка исключений Java.
Если исключение не перехвачено (uncaught)
Происходит следующая последовательность:
- Поток завершается (переходит в состояние
TERMINATED). - JVM вызывает обработчик непойманных исключений (
UncaughtExceptionHandler). - Если обработчик не установлен – стек-трейс выводится в
System.err. - Другие потоки продолжают работу – исключение в одном потоке не останавливает остальные потоки и JVM (если только это не единственный пользовательский поток).
Иерархия поиска UncaughtExceptionHandler
JVM ищет обработчик в следующем порядке:
- Обработчик конкретного потока:
thread.getUncaughtExceptionHandler() - Обработчик группы потоков:
thread.getThreadGroup().uncaughtException() - Обработчик по умолчанию:
Thread.getDefaultUncaughtExceptionHandler() - Если ни один не установлен – стек-трейс печатается в
System.err
Установка обработчиков
Пример: установка UncaughtExceptionHandler
public class UncaughtExceptionDemo {
public static void main(String[] args) throws InterruptedException {
// Обработчик по умолчанию для ВСЕХ потоков
Thread.setDefaultUncaughtExceptionHandler((t, e) ->
System.err.println("[DEFAULT] Поток " + t.getName()
+ " упал: " + e.getMessage()));
// Обработчик для конкретного потока
Thread worker = new Thread(() -> {
throw new RuntimeException("Что-то пошло не так!");
}, "worker-1");
worker.setUncaughtExceptionHandler((t, e) ->
System.err.println("[THREAD] Поток " + t.getName()
+ " упал: " + e.getMessage()));
worker.start();
worker.join();
// Поток без персонального обработчика
Thread worker2 = new Thread(() -> {
throw new ArithmeticException("Деление на ноль");
}, "worker-2");
worker2.start();
// Сработает обработчик по умолчанию
}
}
Особые случаи
| Ситуация | Поведение |
|---|---|
Исключение в пуле потоков (execute()) |
Поток завершается, создаётся новый. UncaughtExceptionHandler вызывается |
Исключение в пуле потоков (submit()) |
Исключение не выводится – оно сохраняется в Future. Вызов future.get() выбросит ExecutionException |
OutOfMemoryError |
Поток завершается. JVM может продолжить работу, если оставшимся потокам хватает памяти |
StackOverflowError |
Поток завершается. Другие потоки не затрагиваются |
Практическая рекомендация
Всегда устанавливайте defaultUncaughtExceptionHandler для логирования:
Пример
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
logger.error("Непойманное исключение в потоке {}", thread.getName(), throwable);
// Отправить алерт в систему мониторинга
});
Аналогия из жизни. Непойманное исключение – это сотрудник, который ушёл с работы не предупредив. Обработчик
UncaughtExceptionHandler– это охранник на проходной, который записывает в журнал, кто и почему ушёл. Без охранника никто не узнает, что сотрудник пропал.
На собеседовании. Ключевые моменты: (1) непойманное исключение завершает только один поток, не всю JVM; (2) для перехвата таких исключений существует
UncaughtExceptionHandler; (3) при использованииExecutorService.submit()исключение «прячется» вFutureи может быть незамеченным – это частый источник скрытых багов. Хорошая практика – всегда вызыватьfuture.get()или использоватьexecute()для задач, не возвращающих результат.