Streams paralelos: desempenho e armadilhas
Ative streams paralelos, entenda o pool de threads comum e evite erros causados por estado mutável compartilhado.
Streams paralelos: desempenho e armadilhas é uma aula grátis de Java Academy no CoddyKit. Esta é a aula 2 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Java Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Java Academy inclui 4 aulas no total.
Ativando fluxos paralelos
Chame .parallel() em qualquer fluxo ou use Collection.parallelStream(). As operações do fluxo são executadas no ForkJoinPool comum (padrão: número de núcleos de CPU - 1 linhas de execução).
long count = list.parallelStream()
.filter(n -> n % 2 == 0)
.count();
System.out.println(count);Quando o paralelismo compensa
Os fluxos paralelos valem a pena quando: o conjunto de dados é grande (100.000 ou mais elementos), a operação em cada elemento é computacionalmente custosa e o fluxo de processamento não mantém estado nem depende da ordem.
// Good candidate: CPU-heavy computation on large dataset
long sum = LongStream.rangeClosed(1, 10_000_000)
.parallel()
.filter(n -> isPrime(n))
.sum();
System.out.println(sum);Quando NÃO usar fluxos paralelos
Evite fluxos paralelos em: coleções pequenas, operações limitadas por E/S (elas bloqueiam as linhas de execução do ForkJoinPool), operações com estado (ordenação, distinção) ou quando a ordem é importante e é caro restaurá-la.
// Bad: I/O bound — blocking ForkJoinPool threads starves other tasks
List<String> result = urls.parallelStream()
.map(url -> httpGet(url)) // BLOCKS the common pool
.collect(Collectors.toList());Falha por estado mutável compartilhado
Os fluxos paralelos executam operações em várias linhas de execução simultaneamente. Modificar uma coleção mutável compartilhada causa condições de corrida e resultados incorretos.
// RACE CONDITION — never do this:
List<Integer> results = new ArrayList<>(); // not thread-safe
numbers.parallelStream().forEach(n -> results.add(n)); // corrupts list!
// Fix:
List<Integer> safe = numbers.parallelStream().collect(Collectors.toList());Operações com estado: sorted e distinct
sorted() e distinct() precisam visualizar todos os elementos antes de produzir a saída, o que limita o paralelismo e frequentemente torna a execução paralela mais lenta que a sequencial.
// sorted() forces collect-all, then sort — parallel overhead usually not worth it:
list.parallelStream().sorted().collect(Collectors.toList());Operações sensíveis à ordem
findFirst() e forEachOrdered() mantêm a ordem de encontro em fluxos paralelos, acrescentando um custo de sincronização. Use findAny() ou forEach() quando a ordem não for importante.
// Faster in parallel (order-insensitive):
Optional<Integer> any = list.parallelStream().filter(n -> n > 10).findAny();
// Slower in parallel (must preserve order):
Optional<Integer> first = list.parallelStream().filter(n -> n > 10).findFirst();Escolhendo o tamanho do conjunto de linhas de execução
O ForkJoinPool comum usa Runtime.getRuntime().availableProcessors() - 1 linhas de execução. Execute fluxos paralelos em um conjunto personalizado enviando-os dentro de uma chamada a ForkJoinPool.invoke().
ForkJoinPool custom = new ForkJoinPool(8);
long result = custom.submit(
() -> LongStream.rangeClosed(1, 1_000_000).parallel().sum()
).get();Comparando o desempenho paralelo e sequencial
Sempre faça medições de desempenho com JMH usando tamanhos de dados realistas. O custo do paralelismo (divisão, coordenação das linhas de execução e combinação) só é justificável quando o tempo de computação é muito maior que esse custo.
@Benchmark
public long sequential() { return LongStream.rangeClosed(1,1_000_000).sum(); }
@Benchmark
public long parallel() { return LongStream.rangeClosed(1,1_000_000).parallel().sum(); }Operações de redução com paralelismo
reduce() e collect() foram projetados para funcionar corretamente em paralelo quando as operações são associativas e o valor de identidade está correto.
// Associative reduce — safe in parallel:
int sum = list.parallelStream().reduce(0, Integer::sum);
// Non-associative: subtraction — NOT safe in parallel:
int bad = list.parallelStream().reduce(0, (a, b) -> a - b); // wrong result!A possibilidade de divisão é importante
Os fluxos paralelos dividem a fonte de dados usando Spliterator. ArrayList e os vetores são divididos em O(1); LinkedList e HashSet são divididos de forma ineficiente, reduzindo a eficiência do paralelismo.
Resumo: lista de verificação dos fluxos paralelos
Antes de usar o paralelismo: (1) conjunto de dados grande, (2) operações limitadas pela CPU, (3) nenhum estado mutável compartilhado, (4) independência da ordem, (5) fonte divisível (vetor/ArrayList). Em caso de dúvida, faça uma medição de desempenho.
Verificação rápida
O que acontece quando o senhor adiciona elementos a uma coleção que não é segura para uso por várias linhas de execução em um forEach de um fluxo paralelo?
Recapitulação
Os fluxos paralelos usam ForkJoinPool. São eficazes para fluxos de processamento grandes, limitados pela CPU, independentes da ordem e sem estado. Nunca altere o estado compartilhado. Faça uma medição de desempenho antes de decidir: o paralelismo costuma ser mais lento para conjuntos de dados pequenos.
Perguntas Frequentes
A aula “Streams paralelos: desempenho e armadilhas” é grátis?
Sim — o texto completo de “Streams paralelos: desempenho e armadilhas” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Java Academy, atualize para CoddyKit PRO. O curso de Java Academy inclui 4 aulas no total.
O que vou aprender em “Streams paralelos: desempenho e armadilhas”?
Ative streams paralelos, entenda o pool de threads comum e evite erros causados por estado mutável compartilhado. Você pratica Java Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar Java Academy?
Nenhuma experiência prévia é necessária. Java Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 2 de 4.
Quanto tempo leva a aula “Streams paralelos: desempenho e armadilhas”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de Java Academy?
Sim. Cada aula de Java Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- flatMap para coleções aninhadas
- Streams paralelos: desempenho e armadilhas
- Spliterator: divisão para paralelismo
- Streams infinitos com iterate e generate