Что такое параллельный stream в Java

Ответ

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

Пример:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.parallelStream()
                .mapToInt(Integer::intValue)
                .sum();

Особенности:

  1. Порядок обработки не гарантируется
  2. Требует потокобезопасных операций
  3. Может быть медленнее sequential для небольших коллекций из-за накладных расходов
  4. Использует общий ForkJoinPool (можно переопределить через custom pool)

Лучше применять для CPU-intensive операций над большими данными. Для IO-bound задач лучше использовать CompletableFuture.

Ответ 18+ 🔞

А, ну это ж про эти ваши параллельные стримы в Java, да? Сидишь такой, пишешь код, и думаешь: "А не распараллелить ли мне эту хуйню, чтобы быстрее работало?" Ну, в теории-то звучит охуенно.

Смотри, в чём прикол. Берёшь обычный листик с циферками:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.parallelStream()
                .mapToInt(Integer::intValue)
                .sum();

Видишь этот parallelStream()? Это как крикнуть: "Эй, народ, собираемся, будем работать дружно!" Вместо того чтобы один поток, как лох, всё пережёвывал, работа делится на несколько. Используется для этого ForkJoinPool — такая хитрая штука, которая задачи дробит и раздаёт.

Но вот тут, ёпта, начинаются нюансы, про которые все забывают, а потом охуевают.

Во-первых, порядок — пошёл нахуй. Ты передал список [1, 2, 3, 4, 5], а обрабатываться они могут в разброс: сначала 4, потом 1, потом 5. Если твоя операция от порядка зависит — пиши пропало, получишь пиздец, а не результат.

Во-вторых, операции должны быть потокобезопасными, ёбана! Нельзя вот так просто взять и изменять общую переменную из всех потоков. Закончится всё тем, что данные превратятся в кашу, а ты будешь неделю искать, где же эта ебучая гонка.

В-третьих, и это самое важное: не для всего это волшебная таблетка! Если у тебя коллекция из пяти элементов, как в примере, то накладные расходы на создание пула, разделение работы и сбор результатов съедят всю выгоду. Получится даже медленнее, чем сделать всё в одном потоке. Это как вызывать овердохуища грузчиков, чтобы передвинуть один стул.

И наконец, он использует общий пул потоков на всю программу. Если ты в десяти местах запустил parallelStream() для тяжёлых задач, они все будут драться за одни и те же ресурсы, и всё встанет колом. Можно, конечно, свой пул настроить, но это уже высший пилотаж.

Так когда его тогда использовать-то, спросишь? Да когда операций — дохуя, и они действительно сложные, "тяжёлые" для процессора. Какую-нибудь математику там, преобразования изображений. А если твоя задача — просто по сети сходить или в базу данных ткнуть (IO-bound задачи), то забудь про parallelStream как страшный сон. Для такого есть CompletableFuture, вот это уже инструмент, блядь.

Короче, инструмент мощный, но не игрушечный. Включил — следи за порядком, за потокобезопасностью и за размером данных. А то будет как всегда: хотел как лучше, а получил неожиданный пиздец и три дня дебага.