Ответ
По умолчанию 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»: если один поток всё сделал, а другой ещё пашется, то первый может у него украсть кусок работы. Для рекурсивного дробления задач, как в параллельных стримах, это овердохуища удобно. В общем, не просто так его выбрали.