Я знаю, где вы намекаете на свой вопрос, и я сделаю все возможное, чтобы объяснить.
Рассмотрим список ввода, состоящий из 8 элементов:
[1, 2, 3, 4, 5, 6, 7, 8]
И предположим, потоки будут parallellize его следующим образом, в действительности они не делают, точный процесс parallellization довольно трудно понять.
Но пока предположите, что они разделили бы размер на два, пока не останется два элемента.
разветвленности деление будет выглядеть следующим образом:
Первое деление:
[1, 2, 3, 4]
[5, 6, 7, 8]
Второе деление:
[1, 2]
[3, 4]
[5, 6]
[7, 8]
Теперь у нас есть четыре порции, которые (в нашей теории) должны обрабатываться четырьмя различными потоками, которые не имеют никакого знания друг друга.
Это действительно может быть исправлено путем синхронизации на каком-то внешнем ресурсе, но тогда вы теряете преимущества параллелиализации, поэтому нам нужно предположить, что мы не синхронизируем, а когда мы не синхронизируем, другие потоки не будут видеть, что другие потоки сделали, поэтому наш результат будет мусором.
Теперь на вопрос о том, где вы спрашиваете о безгражданстве, как его можно правильно обрабатывать параллельно? Как вы можете добавлять элементы, которые обрабатываются параллельно в правильном порядке в список?
Сначала предположим, что простая функция сопоставления, в которой вы указываете карту лямбда i -> i + 10
, а затем печатайте ее с помощью System.out::println
в foreach.
Теперь после второго деления произойдет следующее:
[1, 2] -> [11, 12] -> { System.out.println(11); System.println(12); }
[3, 4] -> [13, 14] -> { System.out.println(13); System.println(14); }
[5, 6] -> [15, 16] -> { System.out.println(15); System.println(16); }
[7, 8] -> [17, 18] -> { System.out.println(17); System.println(18); }
Там нет никакой гарантии, по порядку, кроме, что все элементы, обработанные с помощью одной и той же нити (внутреннее состояние, чтобы не полагаться) обрабатывать в порядке.
Если вы хотите обработать их по порядку, то вам необходимо использовать forEachOrdered
, что гарантирует, что все потоки будут работать в правильном порядке, и вы не потеряете слишком много преимуществ от параллелизма, поскольку это применимо только к до конечного состояния.
Чтобы увидеть, как вы можете добавить элементы parelllized в список, посмотрите на это, с помощью Collectors.toList()
, который предоставляет методы:
- Создание нового списка.
- Добавление значения в список.
- Слияние двух списков.
Теперь произойдет следующее после второго раздела:
Для каждых четырех потоков будет делать следующее (только показывая одну нить здесь):
- Мы были
[1, 2]
.
- Мы сопоставляем его с
[11, 12]
.
- Мы создаем пустой
List<Integer>
.
- В список добавляется
11
.
- В список добавляется
12
.
Теперь все потоки сделали это, и у нас есть четыре списка из двух элементов.
Теперь следующие слияния происходят в указанном порядке:
[11, 12] ++ [13, 14] = [11, 12, 13, 14]
[15, 16] ++ [17, 18] = [15, 16, 17, 18]
- Наконец
[11, 12, 13, 14] ++ [15, 16, 17, 18] = [11, 12, 13, 14, 15, 16, 17, 18]
И, таким образом, результирующий список в порядке и отображение было сделано в параллели. Теперь вы также должны уяснить, почему параллаллизация требует более высокого минимума, а всего лишь два элемента, так как создание новых списков и слияние становятся слишком дорогими.
Я надеюсь, что вы понимаете, почему операции потока должна быть без гражданства, чтобы получить все преимущества parallellization.
В чем вопрос? Вы хотите увидеть, как параллельные потоки Java 8 могут использоваться для выполнения некоторого кода для первых элементов? Или вы хотите понять, как работают алгоритмы fork-join, обходя проблему общих данных? – nosid
Последнее в контексте безгражданских лямбдов. –