【发布时间】:2015-05-22 21:41:23
【问题描述】:
我有一个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 稍后分别由 getter 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,然后接下来的 10,000 条记录有 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 线程,这是我的 PC 上的内核数。
ForkJoinPool pool = new ForkJoinPool(8);
List<Record> output = pool.submit(() ->
stream.collect(Collectors.toList()
)).get();
我希望并行的sorted 操作会尊重流的遇到顺序,并且它会是一个稳定 排序,因为ArrayList 返回的Spliterator 是@987654347 @。
但是,打印出结果 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
output 的 size() 是 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)); -
通过直接处理
Stream来使用常见的ForkJoinPool。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。这个异常还有occurs on Ideone,它似乎正在运行Java 8u25。
更新
我已将我的 JDK 升级到撰写本文时的最新版本 1.8.0_45,问题没有改变。
问题
结果List (output) 中的记录顺序是否因为排序不稳定、遇到顺序未保留或其他原因而出现乱序?
如何确保在创建并行流并对其进行排序时保留相遇顺序?
【问题讨论】:
-
我会尝试制作最简单的程序来重现问题,在最新的 JDK 版本上运行它,如果重现,则提交错误:排序应该是稳定的:它是这样记录的。跨度>
标签: java parallel-processing java-8 java-stream