Можно ли запускать длительные задачи в фоне
Что проверяют
Понимаете ли вы, что «запустить в фоне» это полдела: задачу нужно ограничить, отследить её ошибки и корректно остановить. Следом спрашивают, что будет с такой задачей при рестарте приложения и как вернуть клиенту результат, если задача идёт десять минут.
Как отвечать
Да, можно. Инструменты по возрастанию уровня:
Thread: годится для учебного примера. В проде потоки без пула быстро становятся неуправляемыми;ExecutorService: пул с ограниченным числом потоков. Лучше собиратьThreadPoolExecutorявно: уExecutors.newFixedThreadPoolочередь неограниченная и под наплывом задач растёт память, аnewCachedThreadPoolсоздаёт потоки без лимита;CompletableFuture: цепочки и комбинация результатов. Без своего executorrunAsyncиспользуетForkJoinPool.commonPool(), общий с parallel streams, и долгая блокирующая задача там отнимает потоки у всех;- виртуальные потоки (Java 21):
Executors.newVirtualThreadPerTaskExecutor()для задач, которые в основном ждут сеть или базу. Для счётной нагрузки выигрыша нет; - Spring:
@Asyncвместе с@EnableAsyncи настроеннымThreadPoolTaskExecutor,@Scheduledдля периодических задач.
Дальше скажите про жизненный цикл. Ошибки: исключение в задаче, переданной через submit, прячется в Future и теряется, если никто не вызвал get(). Остановка: shutdown(), затем awaitTermination с таймаутом, а в самой задаче проверка флага прерывания и восстановление его через Thread.currentThread().interrupt() после InterruptedException.
Долгую задачу в веб-приложении не держат внутри HTTP-запроса. Отвечаете 202 Accepted с идентификатором задачи, выполняете её отдельно, клиент опрашивает статус. Если задачу нельзя потерять при деплое, её кладут в персистентную очередь (Kafka, RabbitMQ) или в таблицу, а не в память JVM.
Где спотыкаются
Ставят @Async и вызывают метод из того же класса. Вызов идёт мимо прокси, и метод выполняется синхронно.
Глотают прерывание: catch (InterruptedException e) { e.printStackTrace(); } сбрасывает флаг, и задачу потом не остановить.
Забывают про рестарт. Задача в пуле живёт в памяти процесса и при деплое исчезнет вместе с состоянием.