Рассмотрим List
, который может быть напечатан многими способами с использованием конструкций Java 8:
stringList.forEach(System.out::println); // 1) Iterable.forEach
stringList.stream().forEach(System.out::println); // 2) Stream.forEach (order maintained generally but doc does not guarantee)
stringList.stream().forEachOrdered(System.out::println); // 3) Stream.forEachOrdered (order maintained always)
stringList.parallelStream().forEach(System.out::println); // 4) Parallel version of Stream.forEach (order not maintained)
stringList.parallelStream().forEachOrdered(System.out::println); // 5) Parallel version ofStream.forEachOrdered (order maintained always)
Первый подход (Iterable.forEach
). Итератор коллекции обычно используется и предназначен для fail-fast , что означает, что он выкинет ConcurrentModificationException
, если базовая коллекция будет структурно изменена во время итерации. Как упоминалось в doc для ArrayList
:
Структурная модификация - это любая операция, которая добавляет или удаляет один или несколько элементов или явно изменяет размер массива поддержки;
Таким образом, это означает, что для параметра
ArrayList.forEach
значение допустимо без каких-либо проблем. И в случае одновременного сбора, например.ConcurrentLinkedQueue
итератор будет слабо согласованным , что означает, что действия, переданные вforEach
, допускают внесение даже структурных изменений без исключенияConcurrentModificationException
. Но здесь модификации могут быть или не быть видимыми в этой итерации.Второй подход (
Stream.forEach
) - порядок не определен. Хотя это может не произойти для последовательных потоков, но спецификация не гарантирует этого. Кроме того, действие должно быть неинтерферирующим в природе. Как упоминалось в doc :Поведение этой операции явно недетерминировано. Для параллельных поточных конвейеров эта операция не гарантирует уважения порядка столкновений потока, так как это принесет жертву преимущества параллелизма.
Третий подход (
Stream.forEachOrdered
) - действие будет выполняться в порядке обнаружения потока. Поэтому всякий раз, когда порядок имеет значениеforEachOrdered
, без второй мысли. Как упоминалось в doc :Выполняет действие для каждого элемента этого потока в порядке выполнения потока, если поток имеет определенный порядок встреч.
Во время повторения по синхронизированной коллекции первый подход будет принимать блокировку коллекции один раз и будет удерживать ее во всех методах вызовов к действию, но в случае потоков они используют разделитель коллекции, который не блокировать и полагаться на уже установленные правила невмешательства. В случае, если сбор, поддерживающий поток, изменяется во время итерации, будет выведена
ConcurrentModificationException
или может возникнуть непоследовательный результат.Четвертый подход (Параллельный
Stream.forEach
). Как уже упоминалось, нет гарантии уважать порядок встреч, как ожидалось, в случае параллельных потоков. Возможно, что действие выполняется в разных потоках для разных элементов, которые никогда не могут иметь место сforEachOrdered
.Пятый подход (Параллельный
Stream.forEachOrdered
) -forEachOrdered
будет обрабатывать элементы в порядок, заданный источником, независимо от того, является ли поток последовательным или параллельным. Поэтому нет смысла использовать это с параллельными потоками.