Как реализовать вызов метода в новом потоке с обработкой ошибок и логированием?
Что проверяют
Знаете ли вы, что исключение из другого потока не долетает до вызывающего кода само, и умеете ли работать через ExecutorService и CompletableFuture, а не через голый Thread. Следом спрашивают, как пронести traceId в лог другого потока и как правильно закрыть пул.
Как отвечать
Разберите варианты по мере зрелости:
Thread:try/catchвнутриrun()илиsetUncaughtExceptionHandler. Без этого непойманное исключение печатается в stderr, поток умирает, а вызывающий ничего не узнаёт;ExecutorService.submit(): исключение сохраняется вFutureи всплывает приget()завёрнутым вExecutionException. В обработчик непойманных исключений оно не попадает, так что безget()ошибка теряется молча. Уexecute()поведение обратное;CompletableFuture.supplyAsync(task, executor): ошибки обрабатываются вexceptionally(),handle()илиwhenComplete(). Без явного executor задача уходит вForkJoinPool.commonPool(), который плохо переносит блокирующие вызовы;- виртуальные потоки: с Java 21 для блокирующих задач удобен
Executors.newVirtualThreadPerTaskExecutor()в try-with-resources,ExecutorServiceв современных JDK реализуетAutoCloseable.
Логирование через SLF4J с Logback или Log4j2: log.error("Не удалось обработать заказ {}", id, e), исключение последним аргументом, тогда в логе будет стектрейс. MDC хранится в ThreadLocal и в пул сам не переезжает: снимите MDC.getCopyOfContextMap() перед отправкой и выставьте внутри задачи, в Spring это делают через TaskDecorator. Для @Async-методов без результата ошибки ловит AsyncUncaughtExceptionHandler.
Где спотыкаются
Отправляют задачу через submit() и никогда не вызывают get(). Исключение лежит в Future, в логах пусто, и ошибку ищут неделями.
Логируют e.getMessage() без самого исключения. Стектрейс теряется, а у NullPointerException сообщение бывает null.
Создают new Thread() на каждый вызов, без имени и без пула, а если пул есть, забывают shutdown(). Потоки пула по умолчанию не демоны, и приложение не завершается.