Какое количество потоков по умолчанию использует параллельный Stream в Java?

Ответ

Параллельный стрим (parallelStream()) по умолчанию использует потоки из общего пула (common pool) ForkJoinPool. Уровень параллелизма этого пула равен Runtime.getRuntime().availableProcessors() - 1.

Пример:

List<Integer> numbers = List.of(1, 2, 3, 4, 5);
System.out.println("Доступных процессоров: " + Runtime.getRuntime().availableProcessors());

numbers.parallelStream()
       .forEach(i -> System.out.println(Thread.currentThread().getName()));
// Вывод будет содержать имена потоков вида 'ForkJoinPool.commonPool-worker-N'

Как управлять количеством потоков? По умолчанию изменить нельзя, но можно запустить параллельный стрим внутри своего экземпляра ForkJoinPool:

ForkJoinPool customPool = new ForkJoinPool(4); // Пул с 4 потоками
customPool.submit(() -> 
    list.parallelStream() // Теперь стрим использует customPool, а не commonPool
         .forEach(...)
).join();

Важно: Это глобальная настройка для common pool. Изменять ее для всего приложения через системное свойство java.util.concurrent.ForkJoinPool.common.parallelism не рекомендуется без веских причин.

Ответ 18+ 🔞

Ну слушай, вот есть у тебя этот самый parallelStream(), красавец, такой удобный. А он, сука, работает на общем пуле потоков, на этом ForkJoinPool.commonPool. И знаешь, сколько у него там народу? Ну, по дефолту — сколько у тебя ядер процессора минус один. Runtime.getRuntime().availableProcessors() - 1. Вот такая магия, блядь.

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

List<Integer> numbers = List.of(1, 2, 3, 4, 5);
System.out.println("Доступных процессоров: " + Runtime.getRuntime().availableProcessors());

numbers.parallelStream()
       .forEach(i -> System.out.println(Thread.currentThread().getName()));
// Вывод будет содержать имена потоков вида 'ForkJoinPool.commonPool-worker-N'

Запустишь — и увидишь, как эти ForkJoinPool.commonPool-worker-1 и прочие суетятся, работу твою делают. Всё вроде красиво.

Но вот, допустим, тебе мало этих потоков. Или, наоборот, много — овердохуища, и ты хочешь их придушить. Как быть-то? Ну, глобально-то менять настройки общего пула через java.util.concurrent.ForkJoinPool.common.parallelism — это, блядь, как из пушки по воробьям. Не рекомендую, если только ты не уверен, что весь твой код от этого не накроется медным тазом.

А вот локально — пожалуйста, делай что хочешь! Загони свой параллельный стрим в свой собственный ForkJoinPool. Вот так:

ForkJoinPool customPool = new ForkJoinPool(4); // Пул с 4 потоками
customPool.submit(() -> 
    list.parallelStream() // Теперь стрим использует customPool, а не commonPool
         .forEach(...)
).join();

Вот и всё, ёпта! Теперь твой стрим будет юзать твой пул с четырьмя работягами, а не общак. Главное — не забудь потом за собой прибраться, join() там вызвать или как-то иначе завершить работу, а то потоки так и останутся висеть, как манда с ушами.

Короче, инструмент мощный, но, как и всё в этих ваших итернетах, требует мозгов. А то так и до OutOfMemoryError недалеко, если бездумно плодить пулы. Думай, бошка, думай!