Какой ExecutorService используется в Parallel Stream в Java?

Ответ

По умолчанию Parallel Stream использует общий пул потоков JVM — ForkJoinPool.commonPool().

Ключевые детали:

  • Размер пула по умолчанию равен Runtime.getRuntime().availableProcessors() - 1.
  • Потоки в выводе будут иметь имена вида ForkJoinPool.commonPool-worker-*.

Пример использования по умолчанию:

List<Integer> numbers = List.of(1, 2, 3, 4, 5);
numbers.parallelStream()
       .forEach(n -> System.out.println(Thread.currentThread().getName()));

Как использовать кастомный пул: Вы можете выполнить стрим в своем экземпляре ForkJoinPool для контроля над параллелизмом.

ForkJoinPool customPool = new ForkJoinPool(4); // Пул с 4 потоками
customPool.submit(() -> 
    list.parallelStream().forEach(item -> process(item))
).get();
customPool.shutdown();

Почему ForkJoinPool? Его архитектура work-stealing оптимальна для рекурсивного разделения задач, что идеально подходит для параллельных операций со стримами.

Ответ 18+ 🔞

Вот же ж, опять эти ваши потоки, как будто без них жизнь кончится! Слушай, смотри, чтобы не облажаться с параллельными стримами в Java, нужно чётко понимать одну простую вещь.

По дефолту, когда ты пишешь parallelStream(), вся эта движуха крутится в общем пуле потоков JVM — ForkJoinPool.commonPool(). Это типа как общая кухня в общаге — все туда лезут, посуду не моют, и если кто-то один начинает жарить пельмени, всем остальным может не хватить конфорок.

Важные нюансы, чтобы не сесть в лужу:

  • Сколько там этих «конфорок»? По умолчанию — количество_ядер_процессора - 1. То есть если у тебя 8 ядер, то рабочих потоков будет 7. Хитро, да? Одно ядро оставили системе, чтобы она не охуела совсем.
  • Увидишь в логах имена потоков ForkJoinPool.commonPool-worker-* — всё, приехали, это они, наши герои.

Вот, смотри, как это выглядит в коде, если ничего не трогать:

List<Integer> numbers = List.of(1, 2, 3, 4, 5);
numbers.parallelStream()
       .forEach(n -> System.out.println(Thread.currentThread().getName()));

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

А если тебе этот общий пул как кот нассал в тапки? Ну, мало ли, задачи у тебя долгие, а другие части приложения тоже в этом пуле что-то делают, и всё встаёт колом. Тогда делай свой собственный бассейн, ебать его в сраку!

Как заставить стрим работать в своём, отдельном пуле:

// Создал пул на 4 потока — теперь это твоя личная баня.
ForkJoinPool customPool = new ForkJoinPool(4);
// Засунул туда свою задачу со стримом.
customPool.submit(() -> 
    list.parallelStream().forEach(item -> process(item))
).get(); // get() — это чтобы дождаться результата, а то убежит.
// И не забудь закрыть за собой дверь, а то потоки разбегутся!
customPool.shutdown();

И да, почему именно ForkJoinPool? Да потому что он хитрожопый! У него архитектура «work-stealing»: если один поток всё сделал, а другой ещё пашется, то первый может у него украсть кусок работы. Для рекурсивного дробления задач, как в параллельных стримах, это овердохуища удобно. В общем, не просто так его выбрали.