Подтвердить что ты не робот

Гарантируется ли .collect для параллельных потоков?

Учитывая, что у меня есть список строк List<String> toProcess. Результаты должны быть в порядке, указанном в исходных строках. Я хочу использовать новые параллельные потоки.

Предоставляет ли следующий код , что результаты будут в том же порядке, в каком они были в исходном списке?

// ["a", "b", "c"]
List<String> toProcess;

// should be ["a", "b", "c"]
List<String> results = toProcess.parallelStream()
                                .map(s -> s)
                                .collect(Collectors.toList());
4b9b3361

Ответ 1

TL; DR

Да, заказ гарантирован.

Документация API Stream.collect()

Исходным местом является просмотр того, что определяет, является ли сокращение одновременным или нет. Stream.collect() описание говорит следующее:

Если поток параллелен, а Collector - concurrent, и либо поток неупорядочен, либо коллекционер unordered, тогда будет выполнено параллельное сокращение (см. Collector для получения подробной информации о параллельном сокращении.)

Первое условие выполнено: поток параллелен. Как насчет второго и третьего: является ли Collector одновременным и неупорядоченным?
 

Документация API Collectors.toList()

toList() документация гласит:

Возвращает a Collector, который накапливает входные элементы в новый List. Нет гарантий относительно типа, изменчивости, сериализуемости или безопасности потоков List; если требуется больше контроля над возвращаемым List, используйте toCollection(Supplier).

Returns:
сборщик, который собирает все входные элементы в список, в порядке встречи

Операция, которая работает в порядке поиска, работает с элементами в их первоначальном порядке. Это отменяет параллельность.
 

Код реализации

Проверка реализации Collectors.java подтверждает, что toList() содержит не признаки CONCURRENT или UNORDERED.

public static <T>
Collector<T, ?, List<T>> toList() {
    return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                               (left, right) -> { left.addAll(right); return left; },
                               CH_ID);
}

// ...

static final Set<Collector.Characteristics> CH_ID
        = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));

Обратите внимание, что у коллектора есть набор признаков CH_ID, который имеет только один признак IDENTITY_FINISH. CONCURRENT и UNORDERED не существует, поэтому редукция не может быть параллельной.

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

См. также: Почему параллельный поток собирается последовательно в Java 8

Ответ 2

Вы гарантированно получите элементы в порядке поиска.

Из документации toList:

Возвращает:        сборщик, который собирает все входные элементы в список, в порядке встречи

Подробнее см. java.util.streams для получения дополнительной информации о термине "порядок встреч".

Кроме того, List#spliterator документация требует, чтобы все реализации List производили разделители, которые ORDERED:

Разделитель сообщает Spliterator.SIZED и Spliterator.ORDERED. Реализации должны документировать отчетность дополнительных значений признаков.

Как ни странно, в то время как для интерфейса List требуется iterator() для создания элементов в "правильной последовательности", spliterator() требуется только заказывать, но не обязательно нужно следовать естественному упорядочению списка.

Итак, чтобы ответить на ваш вопрос, список, созданный toList, гарантированно содержит элементы, точно такие как исходный список. Неважно, является ли поток параллельным или последовательным.