Как создаются параллельные потоки (Parallel Stream) в Java?

Ответ

Параллельные потоки в Java Stream API создаются двумя основными способами, которые используют под капотом общий ForkJoinPool.

Способы создания:

  1. Метод Collection.parallelStream() — самый частый способ создать параллельный поток напрямую из коллекции.

    List<String> list = Arrays.asList("A", "B", "C");
    list.parallelStream()
        .forEach(System.out::println); // Элементы обрабатываются параллельно
  2. Метод Stream.parallel() — преобразует существующий последовательный поток в параллельный.

    Stream.of("A", "B", "C")
          .parallel() // Преобразование в параллельный поток
          .forEach(System.out::println);

Важные детали реализации:

  • По умолчанию используется ForkJoinPool.commonPool() — общий пул потоков для всего приложения.
  • Количество потоков в общем пуле равно количество_ядер_процессора - 1.
  • Можно использовать свой ForkJoinPool для изоляции задачи:
    ForkJoinPool customPool = new ForkJoinPool(4);
    customPool.submit(() -> 
        list.parallelStream().forEach(...) // Работает в customPool
    ).get();

Когда использовать: Параллельные потоки эффективны для CPU-интенсивных задач с большими объемами данных, где затраты на распараллеливание окупаются. Для простых операций или IO-задач они могут дать обратный эффект из-за накладных расходов.

Ответ 18+ 🔞

Давай разберём этот ваш параллельный поток, а то как будто про космос речь. Сидишь такой, пишешь на джаве, всё по классике: stream(), filter(), map — красота, душа поёт. А потом бац — нужно быстрее, и тут тебе подсовывают эту штуку — parallelStream(). И вроде как магия, а на деле — обычный ForkJoinPool, только в профиль.

Как его, блядь, создать, этот параллельный поток?

Первым делом, самый частый способ — это взять свою коллекцию и просто, сука, позвать parallelStream(). Всё, приехали. Больше ничего не надо.

List<String> list = Arrays.asList("A", "B", "C");
list.parallelStream()
    .forEach(System.out::println); // А тут элементы уже пляшут как хотят, параллельно, понимаешь?

Второй способ — для тех, кто любит извращения. Есть у тебя обычный, последовательный поток. Ну так возьми и скажи ему: «Стань параллельным, ёпта!» Через метод parallel().

Stream.of("A", "B", "C")
      .parallel() // Вжух, и он уже не тот, стал параллельным ублюдком
      .forEach(System.out::println);

А что там под капотом-то, а?

А под капотом, друг мой, сидит этот самый ForkJoinPool.commonPool() — общий пул на всё приложение, как коммунальная квартира. Потоков в нём обычно: количество твоих ядер процессора минус один. То есть если у тебя 8 ядер, то 7 потоков будут пахать, а одно ядро — за главного считать. Хитро, да?

Но если тебе этот общий пул как серая мышь — не нравится, хочешь изоляции, чтобы твои задачи отдельно шаманили — пожалуйста, создавай свой собственный ForkJoinPool. Это как своя хата, свой огород.

ForkJoinPool customPool = new ForkJoinPool(4); // Четыре работяги, ни больше, ни меньше
customPool.submit(() -> 
    list.parallelStream().forEach(...) // И вот тут всё крутится уже в твоём личном пуле, а не в общем
).get();

Когда это, блядь, применять-то?

А вот тут, мой друг, собака зарыта. Параллельные потоки — это не волшебная таблетка. Они хороши для тяжёлых вычислений, где данных — овердохуища, и каждому элементу нужно по полчаса мозгами шевелить. Тогда распараллеливание окупится. А если у тебя операция — чих-пых, или ещё хуже — ввод-вывод (IO), где поток просто спит и ждёт, то накладные расходы на организацию всей этой цирковой труппы съедят всю выгоду. Получится только медленнее, вот тебе и вся магия. Так что думай головой, прежде чем везде пихать parallel().