У меня есть класс Record:
public class Record implements Comparable<Record>
{
private String myCategory1;
private int myCategory2;
private String myCategory3;
private String myCategory4;
private int myValue1;
private double myValue2;
public Record(String category1, int category2, String category3, String category4,
int value1, double value2)
{
myCategory1 = category1;
myCategory2 = category2;
myCategory3 = category3;
myCategory4 = category4;
myValue1 = value1;
myValue2 = value2;
}
// Getters here
}
Я создаю большой список множества записей. Только второе и пятое значения, i / 10000 и i, используются позже, геттерами getCategory2() и getValue1() соответственно.
List<Record> list = new ArrayList<>();
for (int i = 0; i < 115000; i++)
{
list.add(new Record("A", i / 10000, "B", "C", i, (double) i / 100 + 1));
}
Обратите внимание, что первые 10 000 записей имеют category2 из 0, затем следующие 10000 имеют 1 и т.д., тогда как значения value1 равны 0-114999 последовательно.
Я создаю Stream, который является как parallel, так и sorted.
Stream<Record> stream = list.stream()
.parallel()
.sorted(
//(r1, r2) -> Integer.compare(r1.getCategory2(), r2.getCategory2())
)
//.parallel()
;
У меня есть ForkJoinPool, который поддерживает 8 потоки, которые являются количеством ядер, которые у меня есть на моем ПК.
ForkJoinPool pool = new ForkJoinPool(8);
Я использую трюк , описанный здесь, чтобы отправить задачу обработки потока на мой собственный ForkJoinPool вместо обычного ForkJoinPool.
List<Record> output = pool.submit(() ->
stream.collect(Collectors.toList()
)).get();
Я ожидал, что параллельная операция sorted будет уважать порядок вызова потока и что он будет устойчивым, потому что Spliterator, возвращаемый ArrayList, равен ORDERED.
Однако простой код, который выводит элементы результирующего List output в порядке, показывает, что это не совсем так.
for (Record record : output)
{
System.out.println(record.getValue1());
}
Выход, сгущенный:
0
1
2
3
...
69996
69997
69998
69999
71875 // discontinuity!
71876
71877
71878
...
79058
79059
79060
79061
70000 // discontinuity!
70001
70002
70003
...
71871
71872
71873
71874
79062 // discontinuity!
79063
79064
79065
79066
...
114996
114997
114998
114999
size() of output - это 115000, и все элементы, как представляется, находятся там, в немного другом порядке.
Итак, я написал некоторый код проверки, чтобы убедиться, что sort был стабильным. Если он стабильный, то все значения value1 должны оставаться в порядке. Этот код проверяет порядок, печатает любые расхождения.
int prev = -1;
boolean verified = true;
for (Record record : output)
{
int curr = record.getValue1();
if (prev != -1)
{
if (prev + 1 != curr)
{
System.out.println("Warning: " + prev + " followed by " + curr + "!");
verified = false;
}
}
prev = curr;
}
System.out.println("Verified: " + verified);
Вывод:
Warning: 69999 followed by 71875!
Warning: 79061 followed by 70000!
Warning: 71874 followed by 79062!
Warning: 99999 followed by 100625!
Warning: 107811 followed by 100000!
Warning: 100624 followed by 107812!
Verified: false
Это условие сохраняется, если я выполняю одно из следующих действий:
-
Замените
ForkJoinPoolнаThreadPoolExecutor.ThreadPoolExecutor pool = new ThreadPoolExecutor(8, 8, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10)); -
Используйте общий
ForkJoinPool, обработавStreamнапрямую.List<Record> output = stream.collect(Collectors.toList()); -
Вызов
parallel()после вызоваsorted.Stream<Record> stream = list.stream().sorted().parallel(); -
Вызовите
parallelStream()вместоstream().parallel().Stream<Record> stream = list.parallelStream().sorted(); -
Сортировать по
Comparator. Обратите внимание, что этот критерий сортировки отличается от того, что "естественный" порядок, который я определил для интерфейсаComparable, хотя начиная с результатов уже по порядку с самого начала, результат должен быть тем же самым.Stream<Record> stream = list.stream().parallel().sorted( (r1, r2) -> Integer.compare(r1.getCategory2(), r2.getCategory2()) );
Я могу получить это только для сохранения порядка встреч, если я не сделаю одно из следующих действий на Stream:
- Не вызывайте
parallel(). - Не перегружайте
sorted.
Интересно, что parallel() без сортировки сохранил порядок.
В обоих случаях выше выход:
Verified: true
Моя версия Java - 1.8.0_05. Эта аномалия также возникает в Ideone, которая, как представляется, работает на Java 8u25.
Обновление
Я обновил свой JDK до последней версии с этой записью, 1.8.0_45, и проблема не изменилась.
Вопрос
Является ли порядок записи в результирующем List (output) неуправляемым, потому что сортировка как-то нестабильна, потому что порядок встречи не сохраняется или какая-то другая причина?
Как я могу гарантировать, что порядок встречи сохраняется, когда я создаю параллельный поток и сортирую его?