【发布时间】:2017-10-13 13:28:59
【问题描述】:
Java 实现使用 ReferencePipeline 类,它通过设计支持惰性/延迟 .parallel()。这意味着这些每组代码行完全相同:
// sequential identical lines:
stream.map(mapper).distict().filter(filter).sequential()...
stream.sequential().map(mapper).distict().filter(filter)...
// parallel identical lines:
stream.map(mapper).distict().filter(filter).parallel()...
stream.parallel().map(mapper).distict().filter(filter)...
假设我想使用以下签名构建一个新的自定义流方法:public <T> static Stream<T> myMethod(Stream<T> stream)
并且作为要求,该方法应该存在相同的 .parallel() 惰性/延迟行为。这意味着每组行应该具有完全相同的行为:
// sequential identical lines:
myMethod(stream).sequential()...
myMethod(stream.sequential())...
// parallel identical lines:
myMethod(stream).parallel()...
myMethod(stream.parallel())...
我该怎么做?一个简单的例子会很有用。
public <T> static Stream<T> myMethod(Stream<T> stream) {
// Any implementation that changes the stream
// For simplification lets assume that this extensions
// switches the odd with even positions, or anything else
// that it's simpler and easy to demonstrate
// This prevents parallel to be lazy/late!
Spliterator<T> spliterator = stream.spliterator();
return StreamSupport.stream(new Spliterator<T>() {
// My easy implementation
}, stream.isParallel());
}
请注意,.spliterator() 和 StreamSupport.stream() 的使用会影响此处讨论的并行处理:Understanding sequential vs parallel stream spliterators in Java 8 and Java 9
更新:使用 assertj 的 junit5 测试:
@ParameterizedTest(name="[{index}] {0}/{1}/{2} = {3} --> {4}")
@CsvSource({
"-,-,-,1!,Sequential: default behaviour",
"P,-,-,2+,Parallel: set in stage1",
"-,P,-,2+,Parallel: set in stage2",
"-,-,P,2+,Parallel: set in stage3",
"P,S,-,1!,Sequential: set in stage2",
"P,-,S,1!,Sequential: set in stage3",
"P,S,P,2+,Parallel: set last in stage3",
"S,P,S,1!,Sequential: set last in stage3",
})
void myMethodTest(String stage1f, String stage2f, String stage3f, String expThreads, String name) throws Exception {
Set<String> set1 = new ConcurrentSkipListSet<>();
Set<String> set2 = new ConcurrentSkipListSet<>();
Set<String> set3 = new ConcurrentSkipListSet<>();
int parallelism = 4;
int minExpected = expThreads.equals("1!") ? 1 : 2;
int maxExpected = expThreads.equals("1!") ? 1 : parallelism;
BiFunction<String, Stream<Long>, Stream<Long>> mode = (flag, stream) -> {
switch (flag) {
case "P": return stream.parallel();
case "S": return stream.sequential();
default: return stream;
}
};
Stream<Long> stage1 = mode.apply(stage1f, LongStream.range(0, 1000_000).boxed())
.peek(x -> set1.add(Thread.currentThread().getName()));
Stream<Long> stage2 = mode.apply(stage2f, myMethod(stage1).map(x -> 2*x))
.peek(x -> set2.add(Thread.currentThread().getName()));
Stream<Long> stage3 = mode.apply(stage3f, myMethod(stage2).map(x -> 2*x))
.peek(x -> set3.add(Thread.currentThread().getName()));
new ForkJoinPool(parallelism).submit(() -> {
List<Long> list = stage3.collect(Collectors.toList());
System.out.print("list:" + list.size() + " threads:" + parallelism + " flags:" + stage1f + "/" + stage2f + "/" + stage3f + " ");
}).get();
System.out.print("stage1:" + set1.size() + "/" + maxExpected + " ");
System.out.print("stage2:" + set2.size() + "/" + maxExpected + " ");
System.out.println("stage3:" + set3.size() + "/" + maxExpected + " ");
assertThat(set1.size()).isBetween(minExpected, maxExpected);
assertThat(set2.size()).isBetween(minExpected, maxExpected);
assertThat(set3.size()).isBetween(minExpected, maxExpected);
}
【问题讨论】:
-
你想懒惰地并行评估(自定义)流吗?
-
不知道lazily这个表达式是否是最好用的,但我想要的只是具有与原生流相同的行为,您可以随时添加
.parallel(),一切都会并行完成,而不仅仅是一个部分。StreamSupport.stream()的用法似乎打破了它。 -
您想要在接口
java.util.stream.BaseStream中声明的方法.parallel()的功能 - 用于您的自定义流? -
也许您可以将源流包装在自定义抽象拆分器中,如果
trySplit被调用或在forEachRemaining被调用时将源转换为并行流。 -
谢谢@the8472。有趣的建议。不过
.characteristics()总是先调用,根据特性,.estimatedSize()也可以第二调用。这意味着源拆分器需要在到达trySplit()、forEachRemaining()甚至.tryAdvance()之前从包装源流中实现。
标签: java java-8 java-stream spliterator