DEV Community

Java Efetivo (livro)
Java Efetivo (livro)

Posted on

Item 48: Tenha cuidado ao fazer streams paralelas

Java e Concorrência:

  • Java sempre esteve na vanguarda em facilitar a programação concorrente, oferecendo suporte nativo a threads desde 1996 e evoluindo para incluir bibliotecas como java.util.concurrent e o framework fork-join.

Streams e Paralelismo:

  • Com a introdução das streams no Java 8, ficou fácil paralelizar operações com uma única chamada ao método parallel(). No entanto, mesmo com a facilidade, escrever programas concorrentes corretos e rápidos continua sendo desafiador.

Desempenho e Falhas:

  • Paralelizar pipelines de streams sem cuidado pode levar a falhas de desempenho e liveness (programa que não termina). Um exemplo dado mostra que paralelizar uma stream pode resultar em um aumento significativo do uso da CPU sem resultados visíveis.

Heurísticas e Limitações:

  • A paralelização de streams pode falhar se a heurística usada para dividir o trabalho não for adequada, especialmente em operações como Stream.iterate e limit, onde os custos de calcular elementos extras podem ser muito altos.

Estruturas de Dados Ideais:
Streams sobre ArrayList, HashMap, HashSet, ConcurrentHashMap, arrays e ranges são melhores candidatas para paralelização devido à facilidade de divisão do trabalho entre threads e boa localidade de referência.

Operações Terminais:

  • A efetividade da execução paralela também depende da operação terminal da stream. Operações de redução, como reduce, min, max, count, e sum, e operações de curto-circuito, como anyMatch, allMatch e noneMatch, são melhores para paralelismo.

Especificações Rigorosas:

  • Funções usadas em pipelines paralelas devem ser associativas, não interferentes e sem estado. A violação dessas regras pode causar resultados incorretos ou falhas catastróficas.

Ordens de Execução:

  • Paralelizar uma pipeline pode desordenar a saída, e operações como forEachOrdered podem ser necessárias para preservar a ordem.

Paralelismo Justificado:

  • Paralelize uma stream apenas se houver uma justificativa sólida. O paralelismo inadequado pode resultar em falhas ou desempenho ruim. Sempre meça o desempenho antes e depois da paralelização para garantir que ela seja benéfica.

Exemplo de Eficácia:

  • Um exemplo simples mostrou que paralelizar um cálculo de π(n) reduziu o tempo de execução de 31 para 9,2 segundos, demonstrando que o paralelismo pode ser eficiente em certos cenários.

Uso de SplittableRandom:

  • Para streams de números aleatórios paralelas, prefira SplittableRandom em vez de ThreadLocalRandom ou Random, pois foi projetada especificamente para esse uso e oferece melhor desempenho.

Conclusão:

  • Não tente paralelizar uma stream pipeline sem ter uma boa razão para acreditar que isso preservará a exatidão do cálculo e aumentará a velocidade. Faça testes rigorosos para verificar se o paralelismo é justificado antes de aplicá-lo em código de produção.

EXEMPLOS
1. Exemplo de Stream Sequencial vs. Paralela

  • Este exemplo demonstra a diferença de desempenho entre uma stream sequencial e uma paralela. ParallelStreamExample.java

2. Exemplo de Uso Ineficiente do parallel()

  • Este exemplo mostra como a paralelização pode levar a um comportamento inesperado. InefficientParallelStream.java

3. Exemplo de Uso Eficiente do parallel()

  • Este exemplo mostra uma situação em que a paralelização pode realmente melhorar o desempenho. EfficientParallelStream,java

4. Exemplo de Falhas de Segurança com Streams Paralelas

  • Este exemplo demonstra como uma operação de redução mal implementada pode falhar quando usada em paralelo. ParallelStreamSafetyExample.java

5. Exemplo com SplittableRandom para Streams Paralelas

  • Este exemplo demonstra o uso de SplittableRandom em uma stream paralela para obter melhor desempenho. SplittableRandomExample.java

Esses exemplos ajudam a ilustrar as situações em que a paralelização pode ser útil e também mostram os riscos potenciais de usar parallel() de maneira indiscriminada.

Top comments (0)