Что такое параллельные стримы (parallel streams) в Java и когда их стоит использовать?

«Что такое параллельные стримы (parallel streams) в Java и когда их стоит использовать?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Параллельные стримы в Java — это механизм для выполнения операций над элементами коллекции (или другого источника) параллельно, используя несколько ядер процессора. Они основаны на внутреннем использовании пула потоков ForkJoinPool.commonPool().

Создание параллельного стрима:

List<String> list = Arrays.asList("a", "b", "c");

// Способ 1: Создать параллельный стрим из коллекции
list.parallelStream()
    .forEach(System.out::println);

// Способ 2: Преобразовать существующий последовательный стрим в параллельный
list.stream()
    .parallel()
    .forEach(System.out::println);

Пример: Параллельное вычисление суммы:

long sum = LongStream.rangeClosed(1, 1_000_000)
                     .parallel() // Включаем параллельную обработку
                     .sum();
System.out.println("Сумма: " + sum);

Когда ИСПОЛЬЗОВАТЬ (лучшие случаи):

  • Большой объем данных.
  • «Дорогие» (CPU-intensive) операции над каждым элементом (сложные вычисления).
  • Источник данных легко разделяется (например, ArrayList, массив, IntStream.range).
  • Операция не зависит от порядка (ассоциативная и коммутативная, как sum(), min()) или порядок контролируется (forEachOrdered).
  • Отсутствие состояния (stateless) и побочных эффектов в промежуточных и терминальных операциях.

Когда НЕ ИСПОЛЬЗОВАТЬ (подводные камни):

  • Маленькие коллекции: Накладные расходы на распараллеливание превысят выгоду.
  • Операции с зависимостями по порядку: findFirst(), limit() в параллельном режиме могут работать медленнее.
  • Использование общих изменяемых состояний (shared mutable state): Приводит к состоянию гонки (race condition) и недетерминированным результатам.
    
    // ПЛОХО: Race condition!
    List<Integer> unsafeList = new ArrayList<>();
    IntStream.range(0, 1000).parallel().forEach(unsafeList::add);
    // Размер unsafeList будет меньше 1000

// ХОРОШО: Использовать потокобезопасные коллекции или reduce List safeList = IntStream.range(0, 1000) .parallel() .collect(ArrayList::new, List::add, List::addAll);


*   **Работа с блокирующими I/O операциями:** Потоки в `ForkJoinPool` не предназначены для этого, могут заблокировать все потоки пула.

**Вывод:** Параллельные стримы — мощный инструмент для ускорения CPU-задач на многоядерных системах, но требуют понимания их внутреннего устройства для корректного и эффективного применения.