Java 8流组合器从未调用

时间:2016-06-24 08:11:52

标签: java java-8 java-stream collectors

我正在编写一个自定义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()函数中的断点。我需要设置某种旗帜吗?

2 个答案:

答案 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绝对是过度的。