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

Ответ

Параллельные стримы (parallelStream()) используют под капотом общий ForkJoinPool, доступный через ForkJoinPool.commonPool().

Основные характеристики:

  • Размер пула по умолчанию: равен количеству доступных процессоров (ядер) минус один, т.е. Runtime.getRuntime().availableProcessors() - 1. Минимум — 1.
  • Алгоритм: Work-stealing. Потоки, завершившие свои задачи, могут "воровать" задачи из очереди других потоков, что улучшает балансировку нагрузки.

Как работает ForkJoinPool: Принцип "разделяй и властвуй" (divide-and-conquer). Большая задача рекурсивно разбивается на подзадачи (fork), которые выполняются параллельно, а затем их результаты объединяются (join).

Пример и важные замечания:

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

int sum = numbers.parallelStream() // Используется commonPool
                 .mapToInt(i -> i * 2)
                 .sum(); // Операция сокращения (reduction)

Критические аспекты:

  1. Порядок обработки: Не гарантируется. Операции forEach выполняются в произвольном порядке. Для сохранения порядка используйте forEachOrdered (но это снижает производительность).
  2. Общие ресурсы: Избегайте изменяемого общего состояния (например, несинхронизированных коллекций) внутри лямбда-выражений параллельного стрима — это приведет к состоянию гонки (race condition).
  3. Не для всех задач: Параллелизм выгоден только для больших объемов данных и операций, не блокирующих потоки (CPU-bound задачи). Для IO-bound задач или маленьких коллекций накладные расходы могут перевесить выгоду.

Использование кастомного пула:

ForkJoinPool customPool = new ForkJoinPool(4);
int result = customPool.submit(() -> 
    numbers.parallelStream()
           .mapToInt(i -> i)
           .sum()
).join();

Ответ 18+ 🔞

Да ты посмотри, какая, блядь, интересная хуйня! Эти ваши parallelStream() — они же не с потолка берутся, сука. У них под капотом сидит общий ForkJoinPool, который можно достать через ForkJoinPool.commonPool(). Представляешь? Один на всех, как сортир в коммуналке!

Что там у них внутри, блядь:

  • Сколько этих потоков-то? По умолчанию — сколько у тебя ядер в процессоре, минус одно. То есть Runtime.getRuntime().availableProcessors() - 1. Ну, чтобы один поток, мать его, для себя оставить, наверное. Но меньше одного не будет — а то вообще нихуя не заработает.
  • Как они работают? Алгоритм называется work-stealing. Это когда ленивый поток, который уже всё сделал, может, сука, подкрасться к другому и украсть у него задачку из очереди! Вот такая справедливость, блядь. Чтобы все пахали, а не один в говне.

А сам ForkJoinPool как, блядь, функционирует? Принцип "разделяй и властвуй", только в коде. Большую задачу нахуй разбивают на мелкие (fork), те выполняются параллельно, а потом их обратно склеивают (join). Как пазл, только с потоками.

Вот смотри, пример простой:

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

int sum = numbers.parallelStream() // Вжух! Работает через commonPool
                 .mapToInt(i -> i * 2)
                 .sum(); // А тут всё собирается в кучку

А теперь, сука, самое важное, где все и обоссываются:

  1. Порядок — похуй! Операция forEach может выдать результат в каком угодно порядке, хоть задом наперёд. Если тебе важен порядок — юзай forEachOrdered, но будь готов, что всё поползёт, как улитка в сахаре.
  2. Общие ресурсы — пиздец! Ни в коем случае, блядь, не лезь из лямбды параллельного стрима в какую-нибудь несинхронизированную коллекцию, чтобы что-то там писать. Это гарантированная гонка (race condition), и ты получишь результат, от которого сам от себя охуеешь. Данные перемешаются так, что мама не горюй.
  3. Не всё то золото, что блестит. Параллелить — это не магическая таблетка. Если данных — кот наплакал, или операции в основном тормозные (IO-bound, а не CPU-bound), то накладные расходы на организацию всей этой движухи съедят всю выгоду. Получится только медленнее и сложнее.

Ну а если тебе commonPool как серпом по яйцам? Можно свой, кастомный пул навернуть:

ForkJoinPool customPool = new ForkJoinPool(4); // Сделал на 4 потока
int result = customPool.submit(() -> 
    numbers.parallelStream()
           .mapToInt(i -> i)
           .sum()
).join(); // И ждём, пока всё посчитают

Вот так, блядь. Инструмент мощный, но если использовать его, как мартышка гранату, можно себе всю систему так подвесить, что мало не покажется. Думай головой, а не жопой!