Как асинхронно возвести числа в квадрат и собрать результаты в список на Java
Что проверяют
Умеете ли вы работать с CompletableFuture: запустить задачи, дождаться всех и собрать результат без гонок и потери порядка. Следом спрашивают, в каком пуле это выполняется, что будет при исключении и есть ли смысл в асинхронности для такой дешёвой операции.
Как отвечать
Покажите короткое решение:
List<CompletableFuture<Integer>> futures = numbers.stream()
.map(n -> CompletableFuture.supplyAsync(() -> n * n, executor))
.toList();
List<Integer> squares = CompletableFuture
.allOf(futures.toArray(CompletableFuture[]::new))
.thenApply(v -> futures.stream().map(CompletableFuture::join).toList())
.join();И проговорите детали:
- пул: без второго аргумента
supplyAsyncберётForkJoinPool.commonPool(), общий на всё приложение. Для задач с вводом-выводом нужен свойExecutorService, в Java 21 удобноExecutors.newVirtualThreadPerTaskExecutor()в try-with-resources; - порядок: значения собираются из списка futures, поэтому порядок совпадает с исходным, даже если задачи завершились вразнобой;
- ожидание:
allOfвозвращаетCompletableFuture<Void>, сами значения берутся черезjoinуже после завершения всех; - ошибки:
joinбросает непроверяемоеCompletionException,getбросает проверяемоеExecutionException. Обработку добавляют черезexceptionallyилиhandle.
Честно скажите про смысл: квадрат числа считается за наносекунды, и накладные расходы на задачу в пуле больше самой работы. Для вычислений по коллекции проще parallelStream(), а CompletableFuture оправдан, когда каждая задача ходит в сеть или базу.
Где спотыкаются
Добавляют результаты в обычный ArrayList прямо из thenAccept. Это гонка: элементы теряются или список ломается.
Вызывают join сразу после каждого supplyAsync внутри цикла. Код становится последовательным, асинхронность пропадает.
Пускают блокирующие вызовы в commonPool(). Он маленький, по умолчанию на единицу меньше числа ядер, и медленный HTTP-клиент тормозит всё, что делит этот пул, включая параллельные стримы.