Skip to content

Latest commit

 

History

History
215 lines (193 loc) · 7.64 KB

File metadata and controls

215 lines (193 loc) · 7.64 KB

Streams Paralelos

Objetivo
Develop code that uses parallel streams, including decomposition operation and reduction operation in streams
-
Desenvolver código que usa Streams Paralelos, incluindo operação de decomposição e operação de redução em Streams

Streams podem ser sequenciais ou paralelos. Os sequencias foram vistos na seção anterior, enquanto os paralelos serão apresentados nesta seção. Streams paralelos são executados por mais de uma Thread, geralmente uma quantidade igual à quantidade de núcleos do processador onde a aplicação está sendo executada. Apesar disso, nem sempre é útil utilizá-los. Seu ganho real é em Streams com grande volumes de dados. Em um Stream pequeno, transformá-lo em paralelo pode até causar uma perda de performance.

Ao utilizar qualquer tipo de Stream, é recomendável não executar funções lambdas que causem efeitos colaterais, como mudanças no estado de objetos. Em Streams paralelos essa recomendação é ainda mais importante.

  1. É possível transformar qualquer Stream em paralelo utilizando o método parallel.

    src/org/j6toj8/streams/parallelstreams/Streams_Parallel.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_Parallel.java[role=include]
  2. É possível criar Streams paralelos diretamente em Coleções através do método parallelStream.

    src/org/j6toj8/streams/parallelstreams/Streams_ParallelStream.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_ParallelStream.java[role=include]
  3. Ao utilizar a operação forEach em um Stream paralelo, a ordem de execução não é garantida.

    src/org/j6toj8/streams/parallelstreams/Streams_ParallelForEach.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_ParallelForEach.java[role=include]
    Saída no console
    Sequencial:
    A
    B
    C
    Paralelo:
    B
    C
    A

    O Stream paralelo poderia ter impresso em qualquer ordem, pois não há nenhuma garantia na ordem em que os elementos serão tratados.

  4. A operação forEachOrdered garante que a ordem será mantida mesmo em Streams paralelos.

    src/org/j6toj8/streams/parallelstreams/Streams_ParallelForEachOrdered.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_ParallelForEachOrdered.java[role=include]
    Saída no console
    Sequencial:
    A
    B
    C
    Paralelo:
    A
    B
    C
  5. Em coleções com muitos objetos pode haver um ganho considerável de performance.

    src/org/j6toj8/streams/parallelstreams/Streams_ParallelPerformance.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_ParallelPerformance.java[role=include]
    Saída no console
    Tempo stream sequencial: 9863
    Tempo stream paralelo: 1479

    Perceba que na máquina onde o código foi executado, a execução em paralelo demorou apenas 15% do tempo da execução sequencial. Esse não é um teste minucioso, mas mostra o potencial de Streams paralelos.

  6. Operações intermediárias que alteram o estado de objetos podem gerar resultados inesperados ao serem executadas em paralelo.

    src/org/j6toj8/streams/parallelstreams/Streams_ParallelStatefulOperation.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_ParallelStatefulOperation.java[role=include]
    Saída no console
    Ordem no forEachOrdered:
    A
    B
    C
    Ordem na synchronizedList:
    A
    C
    B

    Perceba que a ordem foi respeitada na última operação do Stream, o forEachOrdered, mas não foi respeitada na execução da operação intermediária map. Isso ocorre porque essa operação intermediária não precisa seguir a ordem dos itens do stream.

  7. Diferente da execução em um Stream sequencial, a operação findAny traz resultados realmente aleatórios ao ser executada em um Stream paralelo.

    src/org/j6toj8/streams/parallelstreams/Streams_ParallelFindAny.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_ParallelFindAny.java[role=include]
    Saída no console
    findAny Sequencial: 7
    findAny Paralelo: 9
  8. Ao realizar uma operação de reduce não há problema caso o acumulador seja associativo.

    src/org/j6toj8/streams/parallelstreams/Streams_ParallelReduceAssociative.java
    link:../../../src/org/j6toj8/streams/parallelstreams/Streams_ParallelReduceAssociative.java[role=include]
    Saída no console
    13440
    13440

    Perceba que o resultado com o Stream sequencial é idêntico ao paralelo. Isso ocorre porque a operação de multiplicação é associativa, ou seja, fazer (2 x 2) x (3 x 3) é o mesmo que fazer (2 x 2 x 3) x 3, ou até mesmo 2 x (2 x 3) x 3.

  9. Ao realizar uma operação de reduce acumuladores não-associativos irá gerar resultados inesperados.

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

    Isso ocorre pois a operação de subtração não é associativa, então o resultado pode variar conforme o Stream for "fatiado" para ser executado em paralelo. Ou seja, fazer 1 - 2 - 3 - 4 não é o mesmo que fazer (1 - 2) - (3 - 4).

  10. Para coletar o resultado de um Stream paralelo em um mapa, utilize a operação toConcurrentMap.

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

    Perceba que o resultados das operações pode ser diferente. Ao utilizar o Collector toConcurrentMap em um Stream paralelo, as operações podem ser executadas em qualquer ordem e não há necessidade de criar múltiplos Map’s para serem combinados posteriormente. Em grandes Streams, isso pode ocasionar em um ganho de performance.

  11. Para coletar o resultado de um Stream paralelo utilize groupingByConcurrent ao invés de groupingBy.

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

    Pelo mesmo motivo do exemplo anterior, a ordem pode variar ao utilizar o groupingByConcurrent, porém pode haver ganho de performance em grandes Streams paralelos, pois a ordem não é necessariamente seguida e não há necessidade de criar múltiplos mapas.

Referências