我正在编写一个自定义java 8收集器,它应该计算具有getValue()
方法的POJO的平均值。这是代码:
public static Collector<BoltAggregationData, BigDecimal[], BigDecimal> avgCollector = new Collector<BoltAggregationData, BigDecimal[], BigDecimal>() {
@Override
public Supplier<BigDecimal[]> supplier() {
return () -> {
BigDecimal[] start = new BigDecimal[2];
start[0] = BigDecimal.ZERO;
start[1] = BigDecimal.ZERO;
return start;
};
}
@Override
public BiConsumer<BigDecimal[], BoltAggregationData> accumulator() {
return (a,b) -> {
a[0] = a[0].add(b.getValue());
a[1] = a[1].add(BigDecimal.ONE);
};
}
@Override
public BinaryOperator<BigDecimal[]> combiner() {
return (a,b) -> {
a[0] = a[0].add(b[0]);
a[1] = a[1].add(b[1]);
return a;
};
}
@Override
public Function<BigDecimal[], BigDecimal> finisher() {
return (a) -> {
return a[0].divide(a[1], 6 , RoundingMode.HALF_UP);
};
}
private final Set<Characteristics> CHARACTERISTICS = new HashSet<Characteristics>(Arrays.asList(Characteristics.CONCURRENT, Characteristics.UNORDERED));
@Override
public Set<Characteristics> characteristics() {
return CHARACTERISTICS;
}
};
在非平行情况下一切正常。但是,当我使用parallelStream()
时,它有时不起作用。例如,给定从1到10的值,它计算(53/9而不是55/10)。调试调试器时,永远不会遇到combiner()函数中的断点。我需要设置某种旗帜吗?
答案 0 :(得分:23)
看起来问题是CONCURRENT
特征,它会做一些你想象不到的事情:
表示此收集器是并发,这意味着 结果容器可以支持累加器功能 从多个同时调用相同的结果容器 线程。
相反调用组合器,同时调用累加器,对所有线程使用相同的BigDecimal[] a
。对a
的访问不是原子的,所以它出错:
Thread1 -> retrieves value of a[0]: 3
Thread2 -> retrieves value of a[0]: 3
Thread1 -> adds own value: 3 + 3 = 6
Thread2 -> adds own value: 3 + 4 = 7
Thread1 -> writes 6 to a[0]
Thread2 -> writes 7 to a[0]
a[0]
7的值应为10. a[1]
可能会发生同样的事情,因此结果可能不一致。
如果删除CONCURRENT
特征,则会使用组合器。
答案 1 :(得分:18)
嗯,这正是您在指定Characteristics.CONCURRENT
时所要求的:
表示此收集器是并发,这意味着结果容器可以支持从多个线程同时使用相同结果容器调用累加器函数。
如果情况与您的Collector
不同,则不应指定该标志。
作为旁注,new HashSet<Characteristics>(Arrays.asList(Characteristics.CONCURRENT, Characteristics.UNORDERED));
指定特征的效率非常低。您可以使用EnumSet.of(Characteristics.CONCURRENT, Characteristics.UNORDERED)
。当您删除错误的并发特征时,您可以使用EnumSet.of(Characteristics.UNORDERED)
或Collections.singleton(Characteristics.UNORDERED)
,但HashSet
绝对是过度的。