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

Почему параллельный поток получается последовательно в Java 8

Почему forEach печатает числа в произвольном порядке, а collect всегда собирает элементы в исходном порядке, даже из параллельного потока?

Integer[] intArray = {1, 2, 3, 4, 5, 6, 7, 8};
List<Integer> listOfIntegers = new ArrayList<>(Arrays.asList(intArray));

System.out.println("Parallel Stream: ");
listOfIntegers
  .stream()
  .parallel()
  .forEach(e -> System.out.print(e + " "));
System.out.println();

// Collectors         
List<Integer> l = listOfIntegers
  .stream()
  .parallel()
  .collect(Collectors.toList());
System.out.println(l);

Вывод:

Parallel Stream: 
8 1 6 2 7 4 5 3 
[1, 2, 3, 4, 5, 6, 7, 8]
4b9b3361

Ответ 1

Здесь происходит два разных типа "упорядочивания", что затрудняет обсуждение.

Один вид - это порядок встреч, который определен в потоковой документации. Хороший способ подумать об этом - это пространственный или левый-правый порядок элементов в исходной коллекции. Если источником является List, рассмотрим более ранние элементы слева от более поздних элементов.

Существует также обрабатывающий или временный порядок, который не определен в документации, но который является порядком времени, в котором элементы обрабатываются разными потоками. Если элементы списка обрабатываются параллельно разными потоками, поток может обрабатывать самый правый элемент в списке перед самым левым элементом. Но в следующий раз это не так.

Даже когда вычисления выполняются параллельно, большинство Collectors и некоторые операции с терминалом тщательно организованы так, что они сохраняют порядок вызова от источника до адресата независимо от временного порядка, в котором различные потоки могут обрабатывать каждый элемент.

Обратите внимание, что операция терминала forEach делает не сохранение порядка встреч. Вместо этого он запускается через любой поток, чтобы произвести следующий результат. Если вы хотите что-то вроде forEach, которое сохраняет порядок встреч, используйте forEachOrdered вместо этого.

См. также Часто задаваемые вопросы лямбда для дальнейшего обсуждения проблем с заказами.

Ответ 2

Метод Collectors.toList указывает, что возвращаемый Collector добавляет элементы в список в порядке выполнения.

Возврат:

a Коллектор, который собирает все входные элементы в список, в порядке выполнения

Не имеет значения, параллелен ли Stream; порядок сохраняется.

Кроме того, если посмотреть на исходный код Collectors, возвращенный Collector вызывает addAll на ArrayList при слиянии и сохраняет заказ. Например. если один поток имеет {1, 2}, а следующий поток имеет {3, 4}, то вызов addAll дает {1, 2, 3, 4}. Кроме того, возвращаемый Collector не имеет атрибута UNORDERED.