Классическая задача: отправили в пул десять задач, разного времени выполнения, и нужно обработать результат каждой как можно раньше. Наивный вариант — собрать List> и вызывать get() по очереди в порядке добавления.

Проблема в том, что порядок добавления не совпадает с порядком завершения. Если первая задача в списке — самая долгая, вызов get() на ней блокирует обработку результатов второй и третьей, даже если они давно готовы и просто ждут своей очереди в списке.

CompletionService решает именно это. Он оборачивает Executor и добавляет внутреннюю очередь завершённых задач: как только задача закончилась, её Future попадает в очередь, а take() отдаёт результаты в порядке завершения, а не в порядке отправки.

ExecutorService pool = Executors.newFixedThreadPool(4); CompletionService ecs = new ExecutorCompletionService<>(pool);

for (Task task : tasks) { ecs.submit(task::call); }

for (int i = 0; i < tasks.size(); i++) { Future done = ecs.take(); process(done.get()); }

Следом спросят про таймаут на ожидание конкретной задачи. У CompletionService нет способа узнать, какая именно задача завершилась, если вам это важно — только сам результат. Если нужно сопоставить результат с исходной задачей, оборачивают в обёртку, которая несёт идентификатор задачи вместе с результатом, либо используют CompletableFuture.anyOf с явной привязкой к задаче через thenApply.

CompletionService не ускоряет вычисления — он просто меняет порядок, в котором вы узнаёте о готовых результатах, и это снимает искусственную блокировку на медленной задаче в начале списка.

Тренажёр: 900+ вопросов и задач, мок с таймером, план повторов

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки