Каким правилом Stream API определяется итоговый режим выполнения, если в одной цепочке несколько раз меняют последовательность на параллельную и обратно?
Итоговый режим определяется последним вызовом, задающим режим: parallel() включает параллельный режим, а sequential() — последовательный. Это свойство всего конвейера, поэтому вызов, расположенный ближе всего к терминальной операции, имеет приоритет над предыдущими переключениями.
Stream API появился в Java 8, чтобы отделить описание обработки данных от способа её выполнения. Один и тот же конвейер можно запускать последовательно или параллельно без переписывания промежуточных операций.
Такой подход позволяет библиотеке использовать разбиение источника, обработку частей и объединение результатов, когда это оправдано. Однако возможность параллельного выполнения не означает автоматического ускорения: стоимость координации может превысить выигрыш.
Режим нередко меняется внутри библиотечного или составного конвейера. Если разработчик предполагает, что ранний вызов parallel() навсегда сделал стрим параллельным, последующий sequential() может незаметно отключить параллельную обработку.
Обратная ошибка тоже опасна: случайный вызов parallel() может увеличить накладные расходы, усложнить порядок побочных эффектов и сделать поведение операций с небезопасным общим состоянием некорректным.
Методы parallel() и sequential() не запускают обработку и не преобразуют элементы. Они задают режим выполнения, который будет использован при последующей терминальной операции.
Если такие методы вызываются несколько раз, действует правило «последний вызов побеждает». Поэтому после последовательности переключений итоговое состояние можно проверить методом isParallel(), не запуская потребление элементов.
Промежуточные операции в примере не выполняются во время построения цепочки. При терминальной операции весь конвейер будет рассматриваться как параллельный, поскольку последним был вызван parallel().
Итоговый режим не гарантирует, что каждая лямбда будет выполняться отдельным потоком или что параллельный вариант окажется быстрее. Эффект зависит от источника, стоимости операций, возможности эффективно разделить источник, размера данных и затрат на синхронизацию.
Переключение режима не делает небезопасные побочные эффекты безопасными. Лямбды должны по возможности быть независимыми от общего изменяемого состояния, а результат должен формироваться через корректные терминальные операции и коллекторы.
Метод обработки получает стрим записей и внутри добавляет вызов parallel(), рассчитывая ускорить тяжёлое преобразование. Вызывающий код передаёт поток небольшого размера, для которого важна предсказуемая последовательная обработка, и после вызова вспомогательного метода добавляет sequential().
Возможны три варианта. Оставить безусловный parallel() — просто, но это навязывает режим вызывающему коду. Полностью убрать переключения — безопаснее с точки зрения поведения, но теряется возможность распараллеливания тяжёлого сценария. Разрешить вызывающему коду явно задать режим — требует немного больше проектирования, зато делает решение предсказуемым.
Практичный вариант — устанавливать нужный режим на границе, где принимается решение о выполнении, и не переключать его скрыто во вспомогательных операциях. После этого режим следует проверять измерениями на реальном объёме данных, а не предполагать ускорение по самому факту вызова parallel().
Нет. Stream-конвейер ленив: промежуточные операции только описывают обработку. Режим, установленный перед терминальной операцией, применяется к запуску всего сформированного конвейера; уже завершённая терминальная операция не может быть изменена последующим переключением.
Нет, он только разрешает выполнение конвейера в параллельном режиме. Реальная эффективность зависит от источника и структуры операций, а небольшая задача может не получить практической выгоды из-за накладных расходов. Параллельность также не означает произвольное нарушение гарантий порядка там, где сама терминальная операция или источник требуют их сохранять.
Нет, режим выполнения и контракт конкретной операции — разные вещи. Последовательный запуск обычно упрощает наблюдаемый порядок обработки, но окончательные гарантии определяются операцией, источником и используемым коллектором; например, свойства результата коллектора не выводятся автоматически только из вызова sequential().