Питання 11 · Розділ 8

Як створити parallel stream?

Є два основні способи створити паралельний стрім:

Мовні версії: English Russian Ukrainian

🟢 Junior Level

Є два основні способи створити паралельний стрім:

// Спосіб 1: З колекції
List<String> list = List.of("a", "b", "c");
list.parallelStream().forEach(System.out::println);

// Спосіб 2: Через метод parallel()
list.stream().parallel().forEach(System.out::println);

Обидва способи роблять одне й те саме. parallelStream() — просто скорочений запис.

Перевірка:

stream.isParallel(); // поверне true

🟡 Middle Level

Способи створення

1. Collection.parallelStream():

Stream<String> pStream = list.parallelStream();

Викликає StreamSupport.stream(collection.spliterator(), true). Прапорець true означає паралельність.

2. Stream.parallel():

Stream<Integer> stream = Stream.of(1, 2, 3).parallel();

Дозволяє зробити будь-який стрім паралельним “на льоту”.

3. StreamSupport:

Stream<T> stream = StreamSupport.stream(mySpliterator, true);

Для низькорівневої оптимізації або своїх структур даних.

Важливий нюанс: останній виклик перемагає

stream.parallel().sequential().parallel(); // буде паралельним
stream.parallel().sequential();            // буде послідовним

Частково паралельних стрімів не буває — весь пайплайн або повністю паралельний, або повністю послідовний.

🔴 Senior Level

Управління потоками через кастомний ForkJoinPool

За замовчуванням використовується ForkJoinPool.commonPool(). У production часто потрібно обмежити паралелізм для конкретного завдання:

ForkJoinPool myPool = new ForkJoinPool(4);
long result = myPool.submit(() ->
    list.parallelStream().mapToInt(this::doWork).sum()
).get();

Як працює: Паралельний стрім перевіряє, чи запущений він всередині ForkJoinWorkerThread. Якщо так — використовує поточний пул замість commonPool. Це ізолює навантаження.

Оптимізації

IntStream.range().parallel(): Один з найефективніших способів — Spliterator для числових діапазонів працює ідеально.

Масив vs List: Arrays.stream(arr).parallel() швидший, ніж list.parallelStream(), бо масив має точний розмір (SIZED) і O(1) доступ за індексом. Це дозволяє Spliterator’у ділити його рівно навпіл без overhead на Iterator.next().

Unordered оптимізація

Якщо джерело впорядковане (LinkedHashSet), паралельний стрім витрачає ресурси на збереження порядку. Якщо порядок не важливий:

linkedHashSet.stream().unordered().parallel()...

// unordered() знімає з ForkJoinPool обов’язок зберігати порядок при merge. // Потоки не повинні синхронізуватися при об’єднанні результатів → менше contention.

Edge Cases

FlatMap Constraints: У паралельному стрімі через flatMap внутрішні стріми обробляються послідовно всередині кожної задачі ForkJoin.

Коли НЕ створювати кастомний ForkJoinPool

  1. Для простих задач — commonPool покриває 95% випадків
  2. Для I/O-bound задач — використовуйте віртуальні потоки (Java 21+) або CompletableFuture
  3. Якщо не контролюєте shutdown — витік потоків при зупинці додатку

Діагностика

  • Thread Names: В лямбді виведіть Thread.currentThread().getName() — побачите ForkJoinPool.commonPool-worker-N
  • VisualVM: Вкладка “Threads” покаже завантаження всіх воркерів commonPool

🎯 Шпаргалка для інтерв’ю

Обов’язково знати:

  • Два способи: collection.parallelStream() та stream.parallel() — роблять одне й те саме
  • За замовчуванням використовується ForkJoinPool.commonPool() з розміром = число ядер - 1
  • parallel() і sequential() можна chaining — останній виклик перемагає
  • Частково паралельних стрімів не буває — весь пайплайн або паралельний, або послідовний
  • Для ізоляції навантаження у production використовують кастомний ForkJoinPool
  • Arrays.stream(arr).parallel() швидший, ніж list.parallelStream() (O(1) доступ, SIZED)
  • .unordered() знімає обов’язок зберігати порядок при merge, знижуючи contention

Часті уточнюючі запитання:

  • Як перевірити, чи паралельний стрім? — Викликати stream.isParallel(), поверне true/false
  • Чи можна зробити частину стріма паралельною? — Ні, прапорець паралелізму застосовується до всього пайплайну
  • Чому Arrays.stream().parallel() швидший? — Масив має SIZED характеристику і O(1) доступ, Spliterator ділить його рівно навпіл
  • Коли використовувати кастомний ForkJoinPool? — У Spring Boot-додатках для ізоляції навантаження між компонентами

Червоні прапорці (НЕ говорити):

  • «Паралельний стрім завжди швидший за звичайний» — на малих даних overhead ForkJoin зробить його повільнішим
  • «Можна контролювати число потоків через parallelStream» — без кастомного ForkJoinPool це неможливо
  • «parallelStream і CompletableFuture — одне й те саме» — це різні абстракції з різними use-cases
  • «Можна зробити один стрім наполовину паралельним» — прапорець бінарний, chaining перемикає весь пайплайн

Пов’язані теми:

  • [[12. Які потенційні проблеми можуть бути з паралельними стрімами]]
  • [[13. Що таке ForkJoinPool і як він пов’язаний з parallel streams]]
  • [[10. Коли використовувати parallel streams]]
  • [[9. Що таке паралельні стріми]]