Что проверяет вопрос про thenApply() и thenApplyAsync() у CompletableFuture
Разница не в том, что делает функция, а в том, какой поток её выполняет.
thenApply() выполняется в потоке, который завершил предыдущую стадию. Если future уже завершён к моменту вызова, код выполнится в потоке, вызвавшем thenApply. thenApplyAsync() всегда уходит в executor — явно переданный или ForkJoinPool.commonPool() по умолчанию.
Разница проявляется, когда предыдущая стадия завершилась в потоке, который нельзя занимать надолго — например, в event loop Netty или в потоке из пула HikariCP. thenApply() выполнит логику там же, thenApplyAsync() выведет её в отдельный пул.
CompletableFuture f1 = supplyAsync(() -> fetch()) .thenApply(x -> x + 1); // тот же поток
CompletableFuture f2 = supplyAsync(() -> fetch()) .thenApplyAsync( x -> x + 1, executor ); // явный executor
Спросят следом: что будет, если стадия уже завершена к моменту вызова thenApply(), а explicit executor в thenApplyAsync() не передан. Ответ: завершённая стадия заставляет thenApply() выполниться в потоке вызывающего кода, а не в потоке, который её завершил — async-версия в такой момент всё равно уйдёт в commonPool().
Выбор между sync и async версией метода CompletableFuture — это выбор, где физически исполнится код, а не про порядок операций.
Тренажёр: 600 вопросов, мок с таймером, план повторов
senior·base — что спрашивают на самом деле
В этом посте были ссылки, но мы их удалили по правилам Сетки