从未调用Java8流组合器
我正在编写一个定制的Java8收集器,它应该计算具有从未调用Java8流组合器,java,java-8,java-stream,collectors,Java,Java 8,Java Stream,Collectors,我正在编写一个定制的Java8收集器,它应该计算具有getValue()方法的POJO的平均值。代码如下: public static Collector<BoltAggregationData, BigDecimal[], BigDecimal> avgCollector = new Collector<BoltAggregationData, BigDecimal[], BigDecimal>() { @Override public
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;
}
};
公共静态收集器avgCollector=新收集器(){
@凌驾
公共供应商(){
返回()->{
BigDecimal[]开始=新的BigDecimal[2];
开始[0]=BigDecimal.ZERO;
start[1]=BigDecimal.ZERO;
返回启动;
};
}
@凌驾
公共双消费者累加器(){
返回(a,b)->{
a[0]=a[0]。添加(b.getValue());
a[1]=a[1]。添加(BigDecimal.ONE);
};
}
@凌驾
公共二进制运算符组合器(){
返回(a,b)->{
a[0]=a[0]。添加(b[0]);
a[1]=a[1]。添加(b[1]);
返回a;
};
}
@凌驾
公共函数完成器(){
返回(a)->{
返回a[0]。除法(a[1],6,取整模式。向上取一半);
};
}
私有最终集特征=新哈希集(Arrays.asList(CHARACTERISTICS.CONCURRENT,CHARACTERISTICS.UNORDERED));
@凌驾
公共集特征(){
收益特性;
}
};
在非并行情况下,这一切都很好。但是,当我使用
parallelStream()
时,它有时不起作用。例如,给定从1到10的值,它计算(53/9而不是55/10)。调试时,调试器从不命中combiner()函数中的断点。有什么我需要设置的标志吗 好吧,这正是您在指定时所要求的:
指示此收集器是并发的,这意味着结果容器可以支持与来自多个线程的同一结果容器并发调用的累加器函数
如果情况并非如此,则与您的收集器一样,您不应该指定该标志
作为旁注,newhashset(Arrays.asList(Characteristics.CONCURRENT,Characteristics.UNORDERED))代码>在指定特性方面效率很低。您只需使用EnumSet.of(Characteristics.CONCURRENT,Characteristics.UNORDERED)
。删除错误的并发特征时,您可以使用EnumSet.of(Characteristics.UNORDERED)
或Collections.singleton(Characteristics.UNORDERED)
,但哈希集
肯定是过火了。看起来问题在于并发
特征,它会做一些你想象不到的事情:
指示此收集器是并发的,这意味着
结果容器可以支持正在执行的累加器函数
与来自多个服务器的同一结果容器并发调用
线程
不是调用组合器,而是同时调用累加器,对所有线程使用相同的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]
上,因此结果可能不一致
如果您删除了并发
特征,组合器将被使用。我对这两个都投了反对票,也感谢您的回答:)我刚刚发现另一个答案更清楚了。也感谢您提供有关枚举集的提示。没关系,我只是注意到您在很短的时间间隔内接受(或尝试接受)了这两个选项,所以我只想消除潜在的混淆。请注意,有更好的方法可以做到这一点,例如a。作为补充说明,您仍然可以使用,如果您正在以任何方式使用匿名类……如果我们正在以其他方式改进代码,则有数组初始值设定项:BigDecimal[]start={BigDecimal.ZERO,BigDecimal.ZERO};返回启动
或()->新的BigDecimal[]{BigDecimal.ZERO,BigDecimal.ZERO}
使用..->表达式
表单,该表单也适用于finisher:a->a[0]。除法(a[1],6,取整模式。对半)
。是否使用原子整数
(或AtomicLong
)而不是一个biginger
?那么可以使用并发
特性吗?@dcsohl在测试期间,我发现用synchronized(这个{…}
将累加器中的to行包围起来也解决了问题。但是我的直觉告诉我,不应该强制使用这个特性,而应该在结果容器以任何方式支持并发操作的情况下使用。@dcsohl:当然,使累加器函数线程安全可以解决这个问题,因为这就是并发
特性所暗示的,这个函数是线程安全的。然而,它也表明,与局部积累加上合并相比,并行评估的性能优势在这里并非如此(这种情况很少发生)。