Parallell Og Asynkron Programmering I Java 8

Parallell kode, som er kode som kjører på mer enn en tråd, var en gang marerittet til mange en erfaren utvikler, Men Java 8 brakte mange endringer som burde gjøre dette ytelsesfremmende trikset mye mer håndterlig.

Parallelle Strømmer

Før Java 8 var det stor forskjell mellom parallell (eller samtidig) kode og sekvensiell kode. Det var også veldig vanskelig å feilsøke ikke-sekvensiell kode. Bare å sette et stoppunkt og gå gjennom strømmen som du normalt ville gjøre, ville fjerne det parallelle aspektet, noe som er et problem hvis det er det som forårsaker feilen.

Heldigvis Ga Java 8 oss strømmer, den største Tingen For Java-utviklere siden bønnen. Hvis Du ikke vet hva De er, Gjør Stream API det mulig å håndtere sekvenser av elementer i en funksjonell sak. (Sjekk vår sammenligning mellom streams og.NET ‘ S LINQ her.) En av fordelene med bekker er at kodenes struktur forblir den samme: enten det er sekvensielt eller samtidig, forblir det like lesbart.

for å få koden til å kjøre parallelt, bruker du bare .parallelStream() i stedet for .stream(), (eller stream.parallel(), hvis du ikke er skaperen av strømmen).

men bare fordi det er enkelt, betyr ikke at parallell kode alltid er det beste valget. Du bør alltid vurdere om det er fornuftig å bruke samtidighet for koden din. Den viktigste faktoren i den avgjørelsen vil være hastigheten: bruk bare samtidighet hvis den gjør koden raskere enn den sekvensielle motparten.

Hastighetsspørsmålet

Parallell kode får sin hastighetsfordel ved å bruke flere tråder i stedet for den eneste som sekvensiell kode bruker. Bestemme hvor mange tråder å lage kan være et vanskelig spørsmål fordi flere tråder ikke alltid resultere i raskere kode: hvis du bruker for mange tråder ytelsen til koden din kan faktisk gå ned.

Det er et par regler som vil fortelle deg hvilket antall tråder du skal velge. Dette avhenger hovedsakelig av hvilken type operasjon du vil utføre og antall tilgjengelige kjerner.

Beregningsintensive operasjoner bør bruke et antall tråder lavere enn eller lik antall kjerner, MENS IO-intensive operasjoner som kopiering av filer ikke har BRUK FOR CPU og kan derfor bruke et høyere antall tråder. Koden vet ikke hvilket tilfelle som gjelder med mindre du forteller det hva du skal gjøre. Ellers vil det standard til et antall tråder som er lik antall kjerner.

det er to hovedtilfeller når det kan være nyttig å kjøre koden parallelt i stedet for sekvensiell: tidkrevende oppgaver og oppgaver kjøres på store samlinger. Java 8 brakte en ny måte å håndtere de store samlingene, nemlig med bekker. Strømmer har innebygd effektivitet ved latskap: de bruker lat evaluering som sparer ressurser ved ikke å gjøre mer enn nødvendig. Dette er ikke det samme som parallellisme, som ikke bryr seg om ressursene så lenge det går raskere. Så for store samlinger trenger du sannsynligvis ikke klassisk parallellisme.

Går Async

Leksjoner Fra JavaScript

Det er en sjelden forekomst at En Java-utvikler kan si at De lærte noe fra Å se På JavaScript, men Når Det gjelder asynkron programmering, Fikk JavaScript faktisk det riktig først. Som et fundamentalt async-språk Har JavaScript stor erfaring med hvor smertefullt Det kan være når det er dårlig implementert. Det startet med callbacks og ble senere erstattet av promises. En viktig fordel med promises er at den har to «kanaler»: en for data og en for feil. Et JavaScript-løfte kan se slik ut:

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

Så når den opprinnelige funksjonen har et vellykket resultat, kalles f1, men hvis en feil ble kastet, blir e1 kalt. Dette kan bringe det tilbake til det vellykkede sporet (f2), eller resultere i en annen feil (e2). Du kan gå fra dataspor til feilspor og tilbake.

Java-versjonen Av JavaScript promises kalles CompletableFuture.

CompletableFuture

CompletableFuture implementerer både grensesnittet Future og CompletionStage. Future eksisterte allerede pre-Java8, men det var ikke veldig utviklervennlig av seg selv. Du kan bare få resultatet av den asynkrone beregningen ved å bruke metoden .get(), som blokkerte resten (gjør async-delen ganske meningsløs mesteparten av tiden), og du måtte implementere hvert mulig scenario manuelt. Å legge til grensesnittet CompletionStage var gjennombruddet som gjorde asynkron programmering I Java brukbar.

CompletionStage er et løfte, nemlig løftet om at beregningen til slutt vil bli gjort. Den inneholder en rekke metoder som lar deg legge ved tilbakeringinger som vil bli utført på den ferdigstillelsen. Nå kan vi håndtere resultatet uten å blokkere.

det er to hovedmetoder som lar deg starte den asynkrone delen av koden din: supplyAsync hvis du vil gjøre noe med resultatet av metoden, og runAsync hvis du ikke gjør det.

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

Tilbakeringinger

Nå kan du legge til disse tilbakeringingene for å håndtere resultatet av supplyAsync.

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

.thenApply ligner på funksjonen .map for strømmer: den utfører en transformasjon. I eksemplet ovenfor tar det resultatet (5) og multipliserer det med 3. Det vil da passere det resultatet (15) lenger ned i røret.

.thenAccept utfører en metode på resultatet uten å transformere det. Det vil heller ikke returnere et resultat. Her vil det skrive ut «resultatet er 15» til konsollen. Det kan sammenlignes med .foreach – metoden for strømmer.

.thenRun bruker ikke resultatet av async-operasjonen og returnerer heller ikke noe, det venter bare å ringe Runnable til forrige trinn er fullført.

Asyncing Din Async

Alle de ovennevnte tilbakeringingsmetodene kommer også i en async-versjon: thenRunAsync, thenApplyAsync, etc. Disse versjonene kan kjøre på sin egen tråd, og de gir deg ekstra kontroll fordi du kan fortelle det som ForkJoinPool å bruke.

hvis du ikke bruker async-versjonen, vil tilbakeringingene alle bli utført på samme tråd.

Når Ting Går Galt

når noe går galt, brukes metoden exceptionally til å håndtere unntaket. Du kan gi den en metode som returnerer en verdi for å komme tilbake på datasporet, eller kaste et (nytt) unntak.

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

Kombiner Og Skriv

du kan kjede flere CompletableFutures ved hjelp av thenCompose – metoden. Uten det ville resultatet bli nestet CompletableFutures. Dette gjør thenCompose og thenApply som flatMap og map for strømmer.

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

Hvis du vil kombinere resultatet av to CompletableFutures, trenger du en metode som er praktisk kalt thenCombine.

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

som du kan se i eksemplet ovenfor, kan resultatet av tilbakeringingen i thenCombine håndteres som en vanlig CompletableFuture med alle dine favoritt CompletionStage metoder.

Konklusjon

Parallell programmering trenger ikke lenger å være en uoverstigelig hindring i jakten på raskere kode. Java 8 gjør prosessen så enkel som mulig, slik at ethvert stykke kode som muligens kan dra nytte av det, kan trekkes, sparker og skriker på alle tråder, inn i multi-core fremtiden som faktisk bare er i dag. Som jeg mener: det er lett å gjøre, så prøv det og se fordelene for deg selv.



+