rinnakkainen ja asynkroninen ohjelmointi Java 8

Parallel code, joka on koodi, joka toimii useammalla kuin yhdellä langalla, oli aikoinaan monen kokeneen kehittäjän painajainen, mutta Java 8 toi paljon muutoksia, joiden pitäisi tehdä tästä suorituskykyä lisäävästä tempusta paljon hallittavampi.

rinnakkaiset Virrat

ennen Java 8: aa rinnakkaisen (tai samanaikaisen) koodin ja juoksevan koodin välillä oli suuri ero. Se oli myös erittäin vaikea debug ei-peräkkäinen koodi. Yksinkertaisesti asettamalla keskeytyspisteen ja käymällä läpi virtauksen, kuten normaalisti, poistaisi rinnakkaisen aspektin, mikä on ongelma, jos se aiheuttaa vian.

onneksi Java 8 antoi meille striimejä, mikä on Javan kehittäjille hienoin asia sitten The Beanin. Jos et tiedä, mitä ne ovat, Stream API: n avulla on mahdollista käsitellä alkuaineiden sekvenssejä funktionaalisessa aineessa. (Tarkista vertailu virtojen ja. NET LINQ täällä.) Yksi virtojen eduista on, että koodin rakenne pysyy samana: olipa se peräkkäin tai samanaikaisesti, se pysyy yhtä luettavissa.

jotta koodi kulkisi rinnakkain, käytetään yksinkertaisesti .parallelStream() eikä .stream(), (tai stream.parallel(), jos et ole virran luoja).

mutta vaikka se on helppoa, se ei tarkoita, että rinnakkaiskoodi olisi aina paras valinta. Sinun pitäisi aina harkita, onko mitään järkeä käyttää concurrency oman koodin. Tärkein tekijä tässä päätöksessä on nopeus: käytä samanaikaisuutta vain, jos se tekee koodistasi nopeamman kuin sen peräkkäinen vastine.

Nopeuskysymys

Rinnakkaiskoodi saa nopeushyötynsä käyttämällä useita kierteitä peräkkäiskoodin käyttämän yhden kierteen sijaan. Päättää, kuinka monta kierteet luoda voi olla hankala kysymys, koska enemmän kierteet eivät aina johda nopeammin koodi: Jos käytät liikaa kierteitä suorituskyky koodi voi todella mennä alas.

on olemassa pari sääntöä, jotka kertovat, minkä määrän kierteitä valita. Tämä riippuu lähinnä siitä, millaista toimintaa haluat suorittaa ja kuinka monta ydintä on käytettävissä.

Laskentaintensiivisissä operaatioissa tulisi käyttää ydinten määrää pienempiä tai yhtä suuria säikeitä, kun taas Io-intensiivisissä operaatioissa, kuten tiedostojen kopioinnissa, ei ole mitään käyttöä suorittimelle ja voi siksi käyttää suurempaa määrää säikeitä. Koodi ei tiedä, mikä tapaus on sovellettavissa, ellet kerro, mitä tehdä. Muuten, se oletuksena useita kierteitä yhtä monta ydintä.

on kaksi päätapausta, joissa voi olla hyödyllistä ajaa koodi rinnakkain peräkkäisen sijaan: aikaa vievät tehtävät ja isoilla kokoelmilla ajettavat tehtävät. Java 8 toi uuden tavan käsitellä noita isoja kokoelmia, nimittäin striimejä. Puroihin on sisäänrakennettu tehokkuus laiskuudella: ne käyttävät laiskaa arviointia, joka säästää resursseja tekemättä enempää kuin on tarpeen. Tämä ei ole sama asia kuin parallelismi, joka ei välitä resursseista, kunhan se menee nopeammin. Isoihin kokoelmiin ei siis todennäköisesti tarvita klassista rinnastusta.

Going Async

Lessons from JavaScript

on harvinaista, että Java-kehittäjä voi sanoa oppineensa jotain katsomalla JavaScriptiä, mutta asynkronisessa ohjelmoinnissa JavaScript sai sen itse asiassa ensin oikein. Pohjimmiltaan async-kielenä Javascriptillä on paljon kokemusta siitä, kuinka tuskallista se voi olla huonosti toteutettuna. Se alkoi soittopyynnöillä ja korvattiin myöhemmin lupauksilla. Lupausten tärkeä etu on se, että siinä on kaksi ”kanavaa”: toinen datalle ja toinen virheille. JavaScript-lupaus saattaa näyttää tältä:

func.then(f1).catch(e1).then(f2).catch(e2);

joten kun alkuperäinen funktio on onnistunut tulos, F1 kutsutaan, mutta jos virhe heitettiin e1 kutsutaan. Tämä saattaa tuoda sen takaisin onnistuneelle radalle (f2) tai johtaa toiseen virheeseen (e2). Voit siirtyä dataradasta virheradalle ja takaisin.

JavaScript promises-ohjelman Java-versio on nimeltään CompletableFuture.

täydennetty tulevaisuus

CompletableFuture toteuttaa sekä Future että CompletionStage rajapyykin. Future oli olemassa jo ennen Java8: aa, mutta se ei sinänsä ollut kovin kehittäjäystävällinen. Asynkronisen laskennan tuloksen sai vain käyttämällä .get() – menetelmää, joka esti loput (jolloin async-osa oli suurimman osan ajasta melko turha) ja jokainen mahdollinen skenaario piti toteuttaa manuaalisesti. CompletionStage – käyttöliittymän lisääminen oli läpimurto, joka teki asynkronisesta ohjelmoinnista Java-kielellä toimivaa.

CompletionStage on lupaus, eli lupaus siitä, että laskenta lopulta tehdään. Se sisältää joukon menetelmiä, joiden avulla voit liittää takaisinsoittoja, jotka suoritetaan, että loppuun. Nyt voimme käsitellä tuloksen estoitta.

on olemassa kaksi päämenetelmää, joiden avulla voit aloittaa koodin asynkronisen osan: supplyAsync jos haluat tehdä jotain menetelmän tuloksella, ja runAsync jos et.

CompletableFuture.runAsync(() → System.out.println("Run async in completable future " + Thread.currentThread()));CompletableFuture.supplyAsync(() → 5);

Callbacks

Now you can add those callbacks to handle the result of your supplyAsync.

CompletableFuture.supplyAsync(() → 5).thenApply(i → i * 3).thenAccept(i → System.out.println("The result is " + i).thenRun(() → System.out.println("Finished."));

.thenApply on samanlainen kuin .map funktio streameille: se suorittaa muunnoksen. Yllä olevassa esimerkissä otetaan tulos (5) ja kerrotaan se 3: lla. Sen jälkeen se siirtää tuon tuloksen (15) kauemmas piippuun.

.thenAccept suorittaa tulokselle menetelmän muuttamatta sitä. Se ei myöskään palauta tulosta. Tässä se tulostaa konsolille ”tulos on 15”. Sitä voidaan verrata virtojen .foreach – menetelmään.

.thenRun ei käytä async-operaation tulosta eikä myöskään palauta mitään, se vain odottaa soittoaan Runnable kunnes edellinen vaihe on valmis.

Async

kaikki edellä mainitut soittotavat ovat myös async-versiossa: thenRunAsync, thenApplyAsync jne. Nämä versiot voivat toimia omalla langallaan ja ne antavat lisäohjausta, koska voit kertoa sille, mitä ForkJoinPool käyttää.

jos et käytä async-versiota, takaisinkutsut suoritetaan kaikki samalla langalla.

kun asiat menevät pieleen

kun jokin menee pieleen, käytetään exceptionally – menetelmää poikkeuksen käsittelemiseksi. Voit antaa sille menetelmän, joka palauttaa arvon päästä takaisin data track, tai heittää (Uusi) poikkeus.

….exceptionally(ex → new Foo()).thenAccept(this::bar);

yhdistele ja sommittele

voit ketjuttaa useita CompletableFutures käyttämällä thenCompose – menetelmää. Ilman sitä tulos olisi sisäkkäinen CompletableFutures. Tämä tekee thenCompose ja thenApply kuten flatMap ja map puroille.

CompletableFuture.supplyAsync(() -> "Hello").thenCompose(s -> CompletableFuture.supplyAsync(() -> s + "World"));

jos haluat yhdistää tuloksen kaksi CompletableFutures, tarvitset menetelmän, jota kutsutaan kätevästi nimellä thenCombine.

future.thenCombine(future2, Integer::sum).thenAccept(value → System.out.println(value));

kuten yllä olevasta esimerkistä näkyy, thenCombine soiton tuloksen voi hoitaa normaalin CompletableFuture tavoin kaikilla suosikki CompletionStage – menetelmillä.

johtopäätös

Rinnakkaisohjelmoinnin ei tarvitse enää olla ylitsepääsemätön este nopeamman koodin metsästyksessä. Java 8 tekee prosessista niin suoraviivaisen kuin voi olla, niin että mikä tahansa koodinpätkä, joka voisi mahdollisesti hyötyä siitä, voidaan vetää, potkien ja huutaen kaikilla säikeillä, moniytimiseen tulevaisuuteen, joka on itse asiassa vain nykypäivä. Jolla tarkoitan: se on helppo tehdä, joten anna sille yrittää ja nähdä sen edut itse.



+