Skip to content

Latest commit

 

History

History
213 lines (191 loc) · 7.15 KB

File metadata and controls

213 lines (191 loc) · 7.15 KB

Parallel Streams

Objective
Develop code that uses parallel streams, including decomposition operation and reduction operation in streams

Streams can be sequential or parallel. Sequential was seen in the previous section, while parallels will be presented in this section. Parallel Streams are executed by more than one Thread, usually equal to the number of processor cores where the application is running. Nevertheless, it is not always useful to use them. Your actual gain is Streams using large volumes of data. In a small Stream, turning it parallel can even cause a loss of performance.

When using any kind Stream, it is recommended not to perform Lambda functions that cause side effects, such as changes in the state of objects. In parallel Streams, this recommendation is even more important.

  1. You can make any Stream parallel using the parallel method.

    src/org/j6toj8/streams/parallelstreams/Streams_Parallel.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_Parallel.java[role=include]
  2. You can create parallel Streams directly in Collections through the parallelStream method.

    src/org/j6toj8/streams/parallelstreams/Streams_ParallelStream.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_ParallelStream.java[role=include]
  3. When using the forEach operation on a parallel Stream, the order of execution is not guaranteed.

    src/org/j6toj8/streams/parallelstreams/Streams_ParallelForEach.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_ParallelForEach.java[role=include]
    console output
    Sequential:
    A
    B
    C
    Parallel:
    B
    C
    A

    The parallel Stream could have printed in any order as there is no guarantee in the order in which the elements will be handled.

  4. The forEachOrdered operation ensures that order will be maintained even in parallel Streams.

    src/org/j6toj8/streams/parallelstreams/Streams_ParallelForEachOrdered.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_ParallelForEachOrdered.java[role=include]
    console output
    Sequential:
    A
    B
    C
    Parallel:
    A
    B
    C
  5. In collections with many objects, there can be a considerable performance gain.

    src/org/j6toj8/streams/parallelstreams/Streams_ParallelPerformance.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_ParallelPerformance.java[role=include]
    console output
    Sequential Stream time: 9863
    Parallel Stream time: 1479

    Note that on the machine where the code was executed, parallel execution took only 15% of the sequential execution time. This is not a thorough test, but it does show the potential of parallel Streams.

  6. Intermediate operations that change the state of objects can produce unexpected results when executed in parallel.

    src/org/j6toj8/streams/parallelstreams/Streams_ParallelStatefulOperation.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_ParallelStatefulOperation.java[role=include]
    console output
    Order on forEachOrdered:
    A
    B
    C
    Order on synchronizedList:
    A
    C
    B

    Note that the order was respected in the last Stream operation, forEachOrdered, but was not respected when performing the map intermediate operation. This is because this intermediate operation does not have to follow the order of stream items.

  7. Unlike running in a sequential Stream, the findAny operation brings really random results when executed in a parallel Stream.

    src/org/j6toj8/streams/parallelstreams/Streams_ParallelFindAny.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_ParallelFindAny.java[role=include]
    console output
    findAny Sequential: 7
    findAny Parallel: 9
  8. When performing a reduce operation there is no problem if the accumulator is associative.

    src/org/j6toj8/streams/parallelstreams/Streams_ParallelReduceAssociative.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_ParallelReduceAssociative.java[role=include]
    console output
    13440
    13440

    Note that the result with sequential Stream is identical to parallel. This is because the multiplication operation is associative, i.e., doing (2 x 2) x (3 x 3) is the same as doing (2 x 2 x 3) x 3, or even 2 x (2x3) x3.

  9. Performing a reduce non-associative accumulator operation will produce unexpected results.

    src/org/j6toj8/streams/parallelstreams/Streams_ParallelReduceNonAssociative.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_ParallelReduceNonAssociative.java[role=include]
    console output
    -18
    8

    This is because the subtraction operation is not associative, so the result may vary as Stream is "sliced" to run in parallel. That is, doing 1 - 2 - 3 - 4 is not the same as doing (1 - 2) - (3 - 4).

  10. To collect the result of a parallel Stream on a map, use the toConcurrentMap operation.

    src/org/j6toj8/streams/parallelstreams/Streams_ParallelToConcurrentMap.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_ParallelToConcurrentMap.java[role=include]
    console output
    toMap: {Roseany=7, Amélia=6, Rodrigo=7, Rinaldo=7, Luiz=4}
    toConcurrentMap: {Amélia=6, Roseany=7, Rodrigo=7, Rinaldo=7, Luiz=4}

    Note that the results of operations may differ. By using Collector toConcurrentMap in a parallel Stream, operations can be performed in any order and there is no need to create multiple Map’s to be combined later. In large Streams, this can lead to a performance gain.

  11. To collect the result of a parallel stream use groupingByConcurrent instead of groupingBy.

    src/org/j6toj8/streams/parallelstreams/Streams_ParallelGroupingByConcurrent.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_ParallelGroupingByConcurrent.java[role=include]
    console output
    {4=[Luiz], 6=[Amélia], 7=[Rinaldo, Rodrigo, Roseany]}
    {4=[Luiz], 6=[Amélia], 7=[Roseany, Rodrigo, Rinaldo]}

    For the same reason as in the previous example, the order may vary when using groupingByConcurrent, but there may be performance gains on large parallel Streams as the order is not necessarily followed and there is no need to create multiple maps.

References