6

I try to understand how reduce() method works exactly with parallel streams and I don't understand why the following code do not return the concatenation of these strings.

This is the code:

public class App {

    public static void main(String[] args) {
        String[] grades = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"};

        StringBuilder concat = Arrays.stream(grades).parallel()
                .reduce(new StringBuilder(),
                        (sb, s) -> sb.append(s),
                        (sb1, sb2) -> sb1.append(sb2));

        System.out.println(concat);

    }
}

The code works only with sequential streams, but with parallel streams it doesn't return the concatenation. The output is different every time. Can someone explain me what's happening there?

Samuel Philipp
  • 10,631
  • 12
  • 36
  • 56
elvis
  • 956
  • 9
  • 33
  • 56
  • 4
    https://stackoverflow.com/q/22577197/2711488  •  https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#Reduction – Holger May 07 '19 at 13:16
  • Well the first thread to get to the reduce stage gets treated first :) if you await a sequential result, you better be using a sequential stream, or at least make it sequential before the reduce operation.. – Yassin Hajaj May 07 '19 at 13:49
  • It’s different every time because “parallel” means multiple threads executing at the same time. Running at the same time means they do not execute in order. – VGR May 07 '19 at 13:58
  • @VGR I understand that parallel menas multiple threads, but if instead of reduce() I'm using collect() it works perfectly even with parallel stream. – elvis May 07 '19 at 14:20
  • 2
    that _is_ the point - one is _mutable_ reduction and the other is _not_. you are using `reduce` like it's a mutable one, and it's not. hint: `accumulator` and `combiner` **must** return a _new_ instance of `StringBuilder`, all the time. – Eugene May 07 '19 at 20:14

1 Answers1

7

The problem lies in the fact you use Stream::reduce for mutable reduction. You should use Stream::collect instead that can be used for safe parallel processing. You may read more about their differences in the official Oracle documentation: Streams: Reduction.

Unlike the reduce method, which always creates a new value when it processes an element, the collect method modifies or mutates an existing value.

Notice the differences in the Javadocs for the two methods:

Therefore, I suggest you do the following:

StringBuilder concat = Arrays.stream(grades)
    .parallel()
    .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append);
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183