Ответ
Параллельные стримы (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)
Критические аспекты:
- Порядок обработки: Не гарантируется. Операции
forEachвыполняются в произвольном порядке. Для сохранения порядка используйтеforEachOrdered(но это снижает производительность). - Общие ресурсы: Избегайте изменяемого общего состояния (например, несинхронизированных коллекций) внутри лямбда-выражений параллельного стрима — это приведет к состоянию гонки (race condition).
- Не для всех задач: Параллелизм выгоден только для больших объемов данных и операций, не блокирующих потоки (CPU-bound задачи). Для IO-bound задач или маленьких коллекций накладные расходы могут перевесить выгоду.
Использование кастомного пула:
ForkJoinPool customPool = new ForkJoinPool(4);
int result = customPool.submit(() ->
numbers.parallelStream()
.mapToInt(i -> i)
.sum()
).join();