Как работают параллельные стримы в Java и почему они могут замедлять или ускорять работу
Что проверяют
Понимаете ли вы, что за параллельностью стоит общий пул, и знаете ли условия, при которых выигрыш вообще возможен.
Как отвечать
parallelStream() делит источник на части через Spliterator, обрабатывает их в общем пуле ForkJoin (commonPool, размер по числу ядер минус один) и объединяет результаты.
Когда ускоряет: много элементов, обработка каждого заметно дороже накладных расходов, источник хорошо делится (массив, ArrayList, диапазон), операции не зависят друг от друга и не имеют побочных эффектов.
Когда замедляет:
- мало данных: разбиение и объединение дороже самой работы;
- плохо делимый источник:
LinkedList,Stream.iterate, чтение из файла построчно; - операции ввода-вывода: поток блокируется, а
commonPoolобщий на всё приложение, и вы подвешиваете чужие задачи; - операции с состоянием:
sorted,distinct,limitтребуют синхронизации и соблюдения порядка; - дорогое объединение: сбор в
Listчерезcollectтребует слияния промежуточных списков; - побочные эффекты: запись в общую коллекцию из нескольких потоков даёт гонку, а
forEachне гарантирует порядок (для порядка естьforEachOrdered, который убивает выигрыш).
Главная практическая мысль: commonPool общий, поэтому параллельный стрим в веб-приложении может неожиданно повлиять на другие запросы. Для операций ввода-вывода параллельные стримы не предназначены, там нужен свой пул или асинхронный код.
Где спотыкаются
Ставят parallel() для ускорения на списке из сотни элементов.
Копят результат в обычный ArrayList из нескольких потоков.
Не знают про общий пул и его влияние на всё приложение.
Не замеряют: без бенчмарка утверждение об ускорении ничем не подкреплено.