Вопрос с собеседования · Java

Почему опасно выполнять блокирующие операции в стадиях CompletableFuture без явного Executor?

SeniorМногопоточность
Коротко

Async-стадии без Executor выполняются в ForkJoinPool.commonPool — он общий на всю JVM и размером примерно число ядер − 1. Блокирующие вызовы (HTTP, БД, get чужого future) занимают его потоки, и встаёт всё, что им пользуется: другие цепочки, parallel stream, библиотеки.

  • Для блокирующих шагов — свой пул: supplyAsync(task, ioExecutor)
  • Симптом: приложение «тормозит» в коде, который не менялся
  • Виртуальные потоки — хороший executor для блокирующих стадий

А ещё В контейнере с 1–2 CPU commonPool совсем крошечный

Подробно

Почему commonPool не подходит для ввода-вывода. Он задуман для коротких вычислительных задач без блокировок; его параллелизм равен availableProcessors() - 1. На машине с 4 ядрами — 3 потока. Три сетевых вызова по секунде — и все async-стадии приложения ждут.

Особенность контейнеров. JVM видит лимит CPU контейнера: при 1 CPU commonPool может состоять из одного потока, а async-задачи вообще выполняться в новом потоке на каждую.

Правильно:

ExecutorService io = Executors.newVirtualThreadPerTaskExecutor();
supplyAsync(() -> http.get(url), io)
    .thenApplyAsync(this::parse, cpuPool);

Блокирующие стадии — в отдельный пул (ограниченный, именованный, или виртуальные потоки), вычислительные можно оставить в commonPool.

То же касается parallelStream() с сетевыми вызовами внутри.

Что хотят услышать

  • Какой пул используется по умолчанию и его размер.
  • Почему блокировки в нём вредят всему приложению.
  • Решение: свой executor.

Частые ошибки

  • Делать блокирующий I/O в commonPool.
  • Думать, что commonPool безграничен.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✓Стадии идут в общий commonPool размером по числу ядер, и блокировки его истощают
  • ✗commonPool однопоточный, и любая блокирующая стадия останавливает его
  • ✗CompletableFuture без Executor вообще не использует потоки
  • ✗Блокировки в commonPool бросают IllegalStateException

Что спросят дальше

  • Чем отличается thenApply от thenApplyAsync?
  • Как обрабатывать исключения в цепочке (exceptionally/handle)?

Разбор темы

Связанные вопросы

Партнёр платформы

Разбор собеседования с sotto

После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.

Разбор собеседования