Rinnakkaiset streamit: suorituskyky ja sudenkuopat
Ota rinnakkaiset streamit käyttöön, tutustu yhteiseen säiepooliin ja vältä jaettuun muuntuvaan tilaan liittyvät virheet.
Rinnakkaiset streamit: suorituskyky ja sudenkuopat on ilmainen Java Academy-oppitunti CoddyKitissä. Tämä on oppitunti 2/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu Java Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Java Academy-kurssilla on yhteensä 4 oppituntia.
Rinnakkaisten streamien käyttöönotto
Kutsukaa .parallel()-metodia missä tahansa streamissa tai käyttäkää metodia Collection.parallelStream(). Stream-operaatiot suoritetaan yhteisessä ForkJoinPool-poolissa (oletus: suorittimen ytimet − 1 säiettä).
long count = list.parallelStream()
.filter(n -> n % 2 == 0)
.count();
System.out.println(count);Milloin rinnakkaisuudesta on hyötyä
Rinnakkaiset streamit ovat hyödyllisiä, kun tietojoukko on suuri (vähintään 100 000 alkiota), kunkin alkion käsittely on laskennallisesti raskasta ja käsittelyputki on tilaton eikä riipu järjestyksestä.
// 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);Milloin rinnakkaisia streameja EI pidä käyttää
Välttäkää rinnakkaisia streameja seuraavissa tapauksissa: pienet kokoelmat, I/O-sidonnaiset operaatiot (ne estävät ForkJoinPoolin säikeitä), tilaa säilyttävät operaatiot (sorting, distinct) tai tilanteet, joissa järjestyksellä on merkitystä ja sen palauttaminen on kallista.
// 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());Virhe jaetussa muuttuvassa tilassa
Rinnakkaiset streamit suorittavat operaatioita samanaikaisesti useissa säikeissä. Jaetun muuttuvan kokoelman muokkaaminen aiheuttaa datakilpailuja ja virheellisiä tuloksia.
// 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());Tilaa säilyttävät operaatiot: sorted ja distinct
sorted() ja distinct() edellyttävät kaikkien alkioiden näkemistä ennen tulosteen tuottamista, mikä rajoittaa rinnakkaisuutta ja tekee rinnakkaisesta käsittelystä usein peräkkäistä hitaampaa.
// sorted() forces collect-all, then sort — parallel overhead usually not worth it:
list.parallelStream().sorted().collect(Collectors.toList());Järjestyksestä riippuvat operaatiot
findFirst() ja forEachOrdered() säilyttävät kohtaamisjärjestyksen rinnakkaisissa streameissa, mikä lisää synkronoinnin kustannuksia. Käyttäkää findAny()- tai forEach()-metodia, kun järjestyksellä ei ole merkitystä.
// 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();Säiepoolin koon valitseminen
Yhteinen ForkJoinPool käyttää Runtime.getRuntime().availableProcessors() - 1 säiettä. Suorittakaa rinnakkaiset streamit mukautetussa poolissa lähettämällä ne ForkJoinPool.invoke()-kutsun sisällä.
ForkJoinPool custom = new ForkJoinPool(8);
long result = custom.submit(
() -> LongStream.rangeClosed(1, 1_000_000).parallel().sum()
).get();Rinnakkaisen ja peräkkäisen käsittelyn vertailumittaus
Mitatkaa aina JMH:lla realistisilla tietojoukoilla. Rinnakkaisuuden yleiskustannukset (jakaminen, säikeiden koordinointi ja yhdistäminen) ovat perusteltuja vain, kun laskennan kesto ylittää yleiskustannukset moninkertaisesti.
@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(); }Reduktio-operaatiot rinnakkaisessa käsittelyssä
reduce() ja collect() on suunniteltu toimimaan oikein rinnakkaisesti, kun operaatiot ovat assosiatiivisia ja identiteettiarvo on oikea.
// 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!Jaettavuudella on merkitystä
Rinnakkaiset streamit jakavat tietolähteen Spliterator-olion avulla. ArrayList ja taulukot jakautuvat ajassa O(1), kun taas LinkedList ja HashSet jakautuvat heikosti, mikä vähentää rinnakkaisuuden tehokkuutta.
Yhteenveto: rinnakkaisten streamien tarkistuslista
Ennen rinnakkaisuuden käyttämistä tarkistakaa: (1) suuri tietojoukko, (2) suoritinsidonnaiset operaatiot, (3) ei jaettua muuttuvaa tilaa, (4) riippumattomuus järjestyksestä, (5) jaettava lähde (taulukko/ArrayList). Jos ette ole varmoja, tehkää vertailumittaus.
Pikatarkistus
Mitä tapahtuu, kun lisäätte säieturvallisuutta vailla olevaan kokoelmaan rinnakkaisen streamin forEach-käsittelyssä?
Kertaus
Rinnakkaiset streamit käyttävät ForkJoinPoolia. Ne sopivat tehokkaasti suuriin, suoritinsidonnaisiin ja järjestyksestä riippumattomiin tilattomiin käsittelyputkiin. Älkää koskaan muuttako jaettua tilaa. Tehkää vertailumittaus ennen käyttöönottoa — rinnakkaisuus on usein pientä tietomäärää käsiteltäessä hitaampaa.
Opi Java tekoälytuutorin avulla — ilmaiseksi
Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.
- Kurssit
- 104
- Oppitunnit
- 374
Usein kysytyt kysymykset
Onko oppitunti ”Rinnakkaiset streamit: suorituskyky ja sudenkuopat” ilmainen?
Kyllä – oppitunnin ”Rinnakkaiset streamit: suorituskyky ja sudenkuopat” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko Java Academy-kurssin, päivitä CoddyKit PROhon. Java Academy-kurssilla on yhteensä 4 oppituntia.
Mitä opin oppitunnilla ”Rinnakkaiset streamit: suorituskyky ja sudenkuopat”?
Ota rinnakkaiset streamit käyttöön, tutustu yhteiseen säiepooliin ja vältä jaettuun muuntuvaan tilaan liittyvät virheet. Harjoittelet Java Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.
Tarvitsenko kokemusta aloittaakseni Java Academy-opiskelun?
Aiempi kokemus ei ole tarpeen. CoddyKitin Java Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 2/4.
Kuinka kauan ”Rinnakkaiset streamit: suorituskyky ja sudenkuopat”-oppitunnin suorittaminen kestää?
Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.
Voinko kirjoittaa ja suorittaa koodia tällä Java Academy-oppitunnilla?
Kyllä. Jokainen Java Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.
Kaikki tämän kurssin oppitunnit
- flatMap sisäkkäisille kokoelmille
- Rinnakkaiset streamit: suorituskyky ja sudenkuopat
- Spliterator: jakaminen rinnakkaisuutta varten
- Infinite Streams with iterate and generate