Reduce返回并行流的不可预测结果

时间:2015-09-28 19:26:48

标签: java parallel-processing java-8 java-stream reduce

我用java stream reduce编写了以下代码示例:

Person reducedPerson = Person.getPersons().stream()
                .parallel()  //will return surprising result
                .reduce(new Person(), (intermediateResult, p2) -> {
                            intermediateResult.setAge(intermediateResult.getAge() + p2.getAge());
                            return intermediateResult;
                        },
                        (ir1, ir2) -> {
                            ir1.setAge(ir1.getAge() + ir2.getAge());
                            return ir1;
                        });
        System.out.println(reducedPerson);

模型:

public class Person {

    String name;

    Integer age;

    public Person() {
        age = 0;
        name = "default";
    }

    //...
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public static Collection<Person> getPersons() {
        List<Person> persons = new ArrayList<>();
        persons.add(new Person("Vasya", 12));
        persons.add(new Person("Petya", 32));
        persons.add(new Person("Serj", 10));
        persons.add(new Person("Onotole", 18));
        return persons;
    }
}

每个代码示例执行返回不同的结果:

例如: Person{name='default', age=256}

Person{name='default', age=248}

我已在combiner内找到了该问题,因为后续流代码正确执行。

请帮助纠正合并器。

P.S。

预期结果:姓名为“默认”且年龄为72岁的人(列表中所有患者的总和)

P.S。

与Integer相同的代码,因为reduce结果可以正常工作:

Integer age = Person.getPersons().stream()
                .parallel()
                .reduce(0, (intermediateResult, p2) -> {
                    intermediateResult = intermediateResult + p2.getAge();
                    return intermediateResult;
                }, (ir1, ir2) -> {
                    System.out.println("combiner");
                    ir1 = ir1 + ir2;
                    return ir1;
                });
        System.out.println(age);

2 个答案:

答案 0 :(得分:6)

To perform mutable reduction, use collect:

reducedPerson = Person.getPersons().parallelStream()
        .collect(
                Person::new,
                (p, q) -> p.setAge(p.getAge() + q.getAge()),
                (p, q) -> p.setAge(p.getAge() + q.getAge())
        );

collect is specifically designed to accumulate into mutable containers safely even in parallel.

答案 1 :(得分:2)

正如鲍里斯所指出的,问题是流中的突变。

  

大多数流操作接受描述用户指定的参数   行为,例如lambda表达式w - &gt; w.getWeight()传递给   上面的例子中的mapToInt。为了保持正确的行为,这些   行为参数:

     
      
  • 必须是非干扰的(它们不会修改流源);并在
  •   
  • 大多数情况必须是无国籍的(他们的结果不应该依赖于任何情况   在执行流管道期间可能会更改的状态。)
  •   

https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html

这是使用reduce的版本,以及使用maptoint和sum的更直接的版本。

class gstackoverflow{
  public static void main(String... args) {
    Person reducedPerson = Person.getPersons().stream()
        .parallel()  //will NOT return surprising result
        .reduce(new Person("default",0),
            (ir1, ir2) -> //no longer mutates
                new Person(String.join(",", ir1.getName(), ir2.getName()), ir1.getAge() + ir2.getAge())
        );
    System.out.println(reducedPerson);

    //here is a clean(er) way to do it:
    int totalAge = Person.getPersons().stream()
        .parallel()  //will NOT return surprising result
        .mapToInt(Person::getAge)
        .sum();
    System.out.println(totalAge);
  }
}

class Person {//no longer mutable

  public String getName() {
    return name;
  }

  public Integer getAge() {
    return age;
  }

  final String name;

  final Integer age;

  //no args constructor removed
  public Person(String name, Integer age) {
    this.name = name;
    this.age = age;
  }

  public static Collection<Person> getPersons() {
    List<Person> persons = new ArrayList<>();
    persons.add(new Person("Vasya", 12));
    persons.add(new Person("Petya", 32));
    persons.add(new Person("Serj", 10));
    persons.add(new Person("Onotole", 18));
    return persons;
  }
  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder("Person{");
    sb.append("name='").append(name).append('\'');
    sb.append(", age=").append(age);
    sb.append('}');
    return sb.toString();
  }
}