Multithreading and order

Multithreading and order #

Multithreading #

We can allow a whole stream pipeline to be executed in a concurrent or parallel fashion, for instance with the instance method Stream.parallel, as follows:

Example.

List<Integer>list = List.of(1, 2, 3);

// Contains [3,4,5]
List<Integer> ouputList = list.stream()
                            .parallel()
                            .map(i -> i + 2)
                            .toList();

This allows the JVM to take advantage of available resources to execute operations (like the map operation in this example) in several threads, concurrently.

Order #

From the Javadoc of the package java.util.stream, if the source of a stream is ordered (e.g. a List), then the effect of an operation on the stream is usually predictable.

Indeed, most operations are constrained to operate on the elements in their “encounter order”. For instance, if the source of a stream is a List containing [1, 2, 3], then the result of executing map(x -> x * 2) must be [2, 4, 6].

However, in some cases, the terminal operation forEach may ignore the encounter order. If the stream is executed sequentially (which is the case by default), then this usually has no effect on execution. But if the stream is executed in parallel, then this is likely to affect the output.

In such a case, the terminal operation forEachOrdered may be used instead of forEach, in order to force this operation to respect the encounter order.

Example.

List<Integer>list = List.of(1, 2, 3);

// Must output `2` `4` and `6, in this order.
list.stream()
      .parallel()
      .map(x -> x * 2)
      .forEachOrdered(System.out::println);

Besides, it is possible to relax the ordering constraint with the instance method Stream.unordered(). This may improve performance for pipelines with costly operations, in scenarios where the order of processing is irrelevant.

Example. In this example, the order in which the elements of the stream are processed is irrelevant because:

  • the map applies a pure function, and
  • the stream is collected as a set.

This is why calling the unordered method is safe.

List<MyClass>list = getList();

list.stream()
      .parallel()
      .unordered()
      .map(o -> expensivePureFunction(o))
      .collect(Collectors.toSet());