Stream paralleli: prestazioni e insidie
Attivi gli stream paralleli, comprenda il pool di thread comune ed eviti i bug causati dallo stato mutabile condiviso.
Stream paralleli: prestazioni e insidie è una lezione Java Academy gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Java Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Java Academy include 4 lezioni in totale.
Abilitare gli stream paralleli
Chiami .parallel() su qualsiasi stream oppure usi Collection.parallelStream(). Le operazioni dello stream vengono eseguite nel ForkJoinPool comune (impostazione predefinita: numero di core della CPU - 1 thread).
long count = list.parallelStream()
.filter(n -> n % 2 == 0)
.count();
System.out.println(count);Quando il parallelismo è vantaggioso
Gli stream paralleli sono vantaggiosi quando: il dataset è grande (100.000 o più elementi), l'operazione su ogni elemento è onerosa dal punto di vista computazionale e la pipeline è senza stato e indipendente dall'ordine.
// 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 NON usare gli stream paralleli
Eviti gli stream paralleli per: raccolte piccole, operazioni vincolate all'I/O (bloccano i thread del ForkJoinPool), operazioni con stato (sorting, distinct) o quando l'ordine è importante e ripristinarlo è costoso.
// 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());Bug dovuto allo stato mutabile condiviso
Gli stream paralleli eseguono le operazioni contemporaneamente su più thread. Modificare una raccolta mutabile condivisa causa race condition e risultati errati.
// 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());Operazioni con stato: sorted e distinct
sorted() e distinct() devono vedere tutti gli elementi prima di produrre l'output, limitando il parallelismo e rendendo spesso l'esecuzione parallela più lenta di quella sequenziale.
// sorted() forces collect-all, then sort — parallel overhead usually not worth it:
list.parallelStream().sorted().collect(Collectors.toList());Operazioni sensibili all'ordine
findFirst() e forEachOrdered() mantengono l'ordine di incontro negli stream paralleli, con un costo aggiuntivo di sincronizzazione. Usi findAny() o forEach() quando l'ordine non è 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();Scegliere la dimensione del pool di thread
Il ForkJoinPool comune usa Runtime.getRuntime().availableProcessors() - 1 thread. Esegua gli stream paralleli in un pool personalizzato inviandoli all'interno di una chiamata a ForkJoinPool.invoke().
ForkJoinPool custom = new ForkJoinPool(8);
long result = custom.submit(
() -> LongStream.rangeClosed(1, 1_000_000).parallel().sum()
).get();Confronto delle prestazioni tra parallelo e sequenziale
Esegua sempre il benchmark con JMH e dimensioni dei dati realistiche. L'overhead del parallelismo (suddivisione, coordinamento dei thread e combinazione) è giustificato solo quando il tempo di calcolo supera di gran lunga tale overhead.
@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(); }Operazioni di riduzione con il parallelismo
reduce() e collect() sono progettati per funzionare correttamente in parallelo quando le operazioni sono associative e il valore identità è corretto.
// 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!L'importanza della suddivisibilità
Gli stream paralleli suddividono la sorgente dei dati usando Spliterator. ArrayList e gli array si suddividono in O(1); LinkedList e HashSet si suddividono male, riducendo l'efficienza del parallelismo.
Riepilogo: checklist degli stream paralleli
Prima di usare il parallelismo, verifichi: (1) dataset grande, (2) operazioni vincolate alla CPU, (3) assenza di stato mutabile condiviso, (4) indipendenza dall'ordine, (5) sorgente suddivisibile (array o ArrayList). In caso di dubbi, esegua un benchmark.
Verifica rapida
Cosa succede quando si aggiungono elementi a una raccolta non thread-safe in un forEach di uno stream parallelo?
Riepilogo
Gli stream paralleli usano il ForkJoinPool. Sono efficaci per pipeline grandi, vincolate alla CPU, indipendenti dall'ordine e senza stato. Non modifichi mai lo stato condiviso. Esegua un benchmark prima di adottarli: per dati di piccole dimensioni il parallelismo è spesso più lento.
Domande Frequenti
La lezione «Stream paralleli: prestazioni e insidie» è gratuita?
Sì — il testo completo di «Stream paralleli: prestazioni e insidie» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Java Academy, passa a CoddyKit PRO. Il corso Java Academy include 4 lezioni in totale.
Cosa imparerò in «Stream paralleli: prestazioni e insidie»?
Attivi gli stream paralleli, comprenda il pool di thread comune ed eviti i bug causati dallo stato mutabile condiviso. Eserciti Java Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare Java Academy?
Non è richiesta alcuna esperienza precedente. Java Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.
Quanto tempo richiede la lezione «Stream paralleli: prestazioni e insidie»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione Java Academy?
Sì. Ogni lezione Java Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- flatMap per collezioni annidate
- Stream paralleli: prestazioni e insidie
- Spliterator: suddivisione per il parallelismo
- Stream infiniti con iterate e generate