Guide to CompletableFuture

1. Introduksjon

Denne opplæringen er en guide til funksjonaliteten og brukssakene til Fullførbar fremtid klasse som ble introdusert som en forbedring av Java 8 Concurrency API.

2. Asynkron beregning i Java

Asynkron beregning er vanskelig å resonnere om. Vanligvis vil vi tenke på hvilken som helst beregning som en serie trinn, men når det gjelder asynkron beregning, handlinger representert som tilbakeringinger har en tendens til å være enten spredt over koden eller dypt nestet i hverandre. Ting blir enda verre når vi trenger å håndtere feil som kan oppstå under ett av trinnene.

De Framtid grensesnitt ble lagt til i Java 5 for å tjene som et resultat av en asynkron beregning, men det hadde ingen metoder for å kombinere disse beregningene eller håndtere mulige feil.

Java 8 introduserte Fullførbar fremtid klasse. Sammen med Framtid grensesnitt, implementerte den også CompletionStage grensesnitt. Dette grensesnittet definerer kontrakten for et asynkront beregningstrinn som vi kan kombinere med andre trinn.

Fullførbar fremtid er samtidig en byggestein og et rammeverk, med omtrent 50 forskjellige metoder for å komponere, kombinere og utføre asynkrone beregningstrinn og håndteringsfeil.

En så stor API kan være overveldende, men disse faller for det meste i flere klare og tydelige brukssaker.

3. Bruke Fullførbar fremtid som en enkel Framtid

Først av alt, Fullførbar fremtid klasse implementerer Framtid grensesnitt, slik at vi kan bruk den som en Framtid implementering, men med ytterligere fullføringslogikk.

For eksempel kan vi lage en forekomst av denne klassen med en ikke-arg-konstruktør for å representere noe fremtidig resultat, dele det ut til forbrukerne, og fullføre det på et tidspunkt i fremtiden ved hjelp fullstendig metode. Forbrukerne kan bruke metode for å blokkere den gjeldende tråden til dette resultatet er gitt.

I eksemplet nedenfor har vi en metode som skaper en Fullførbar fremtid eksempel, snurrer deretter av noen beregning i en annen tråd og returnerer Framtid umiddelbart.

Når beregningen er ferdig, fullfører metoden Framtid ved å gi resultatet til fullstendig metode:

offentlig Fremtidige beregneAsync () kaster InterruptedException {CompletableFuture completeableFuture = new CompletableFuture (); Executors.newCachedThreadPool (). Submit (() -> {Thread.sleep (500); completeableFuture.complete ("Hello"); return null;}); retur fullførbarFuture; }

For å spinne av beregningen bruker vi Leder API. Denne metoden for å lage og fullføre en Fullførbar fremtid kan brukes sammen med hvilken som helst samtidighetsmekanisme eller API, inkludert rå tråder.

Legg merke til det de calcAsync metoden returnerer a Framtid forekomst.

Vi kaller bare metoden, mottar Framtid eksempel, og ring metode på den når vi er klare til å blokkere for resultatet.

Vær også oppmerksom på at metoden kaster noen avmerkede unntak, nemlig Utførelse Unntak (innkapslet et unntak som skjedde under en beregning) og InterruptedException (et unntak som indikerer at en tråd som utfører en metode ble avbrutt):

Future completeableFuture = calcAsync (); // ... String result = completeableFuture.get (); assertEquals ("Hei", resultat);

Hvis vi allerede vet resultatet av en beregning, vi kan bruke det statiske fullført metode med et argument som representerer et resultat av denne beregningen. Følgelig ble den metoden for Framtid vil aldri blokkere, og returnerer umiddelbart dette resultatet i stedet:

Future completeableFuture = CompletableFuture.completedFuture ("Hei"); // ... String result = completeableFuture.get (); assertEquals ("Hei", resultat);

Som et alternativ scenario kan det være lurt avbryte utførelsen av en Framtid.

4. Fullførbar fremtid med innkapslet beregningslogikk

Koden ovenfor lar oss velge hvilken som helst mekanisme for samtidig kjøring, men hva om vi vil hoppe over denne kjeleplaten og bare utføre litt kode asynkront?

Statiske metoder runAsync og supplyAsync la oss lage en Fullførbar fremtid instans ut av Kjørbar og Leverandør funksjonelle typer tilsvarende.

Både Kjørbar og Leverandør er funksjonelle grensesnitt som gjør det mulig å overføre forekomster som lambdauttrykk takket være den nye Java 8-funksjonen.

De Kjørbar grensesnitt er det samme gamle grensesnittet som brukes i tråder, og det tillater ikke å returnere en verdi.

De Leverandør grensesnitt er et generelt funksjonelt grensesnitt med en enkelt metode som ikke har noen argumenter og returnerer en verdi av en parameterisert type.

Dette gjør at vi kan gi en forekomst av Leverandør som et lambdauttrykk som gjør beregningen og returnerer resultatet. Det er så enkelt som:

CompletableFuture future = CompletableFuture.supplyAsync (() -> "Hei"); // ... assertEquals ("Hello", future.get ());

5. Behandlingsresultater av asynkrone beregninger

Den mest generiske måten å behandle resultatet av en beregning på er å mate det til en funksjon. De deretterSøk metoden gjør akkurat det; den aksepterer en Funksjon bruker den for å behandle resultatet, og returnerer a Framtid som har en verdi som returneres av en funksjon:

CompletableFuture completeableFuture = CompletableFuture.supplyAsync (() -> "Hei"); CompletableFuture future = completeFuture .thenApply (s -> s + "Verden"); assertEquals ("Hello World", future.get ());

Hvis vi ikke trenger å returnere en verdi nedover Framtid kjede, kan vi bruke en forekomst av Forbruker funksjonelt grensesnitt. Den eneste metoden tar en parameter og returnerer tomrom.

Det er en metode for denne brukssaken i Fullførbar fremtid De deretterAksepter metoden mottar en Forbruker og overfører resultatet av beregningen. Så finalen future.get () kall returnerer en forekomst av Tomrom type:

CompletableFuture completeableFuture = CompletableFuture.supplyAsync (() -> "Hei"); CompletableFuture future = completeFuture .thenAccept (s -> System.out.println ("Beregning returnert:" + s)); future.get ();

Til slutt, hvis vi verken trenger verdien av beregningen eller ønsker å returnere noen verdi på slutten av kjeden, så kan vi passere en Kjørbar lambda til deretter løp metode. I det følgende eksemplet skriver vi bare ut en linje i konsollen etter at vi har ringt future.get ():

CompletableFuture completeableFuture = CompletableFuture.supplyAsync (() -> "Hei"); CompletableFuture future = completeableFuture .thenRun (() -> System.out.println ("Beregning ferdig.")); future.get ();

6. Kombinere futures

Den beste delen av Fullførbar fremtid API er evne til å kombinere Fullførbar fremtid forekomster i en kjede av beregningstrinn.

Resultatet av denne kjedingen er i seg selv a Fullførbar fremtid som muliggjør videre lenking og kombinasjon. Denne tilnærmingen er allestedsnærværende i funksjonelle språk og blir ofte referert til som et monadisk designmønster.

I det følgende eksemplet bruker vi deretterKomponer metode for å kjede to Fremtid sekvensielt.

Legg merke til at denne metoden tar en funksjon som returnerer a Fullførbar fremtid forekomst. Argumentet for denne funksjonen er resultatet av forrige beregningstrinn. Dette lar oss bruke denne verdien i den neste Fullførbar fremtid’S lambda:

CompletableFuture completeableFuture = CompletableFuture.supplyAsync (() -> "Hello") .thenCompose (s -> CompletableFuture.supplyAsync (() -> s + "World")); assertEquals ("Hello World", completeableFuture.get ());

De deretterKomponer metode, sammen med deretterAnvend, implementere grunnleggende byggesteiner i det monadiske mønsteret. De forholder seg nært til kart og flatMap metoder for Strøm og Valgfri klasser også tilgjengelig i Java 8.

Begge metodene mottar en funksjon og bruker den på beregningsresultatet, men deretterKomponer (flatMap) -metoden mottar en funksjon som returnerer et annet objekt av samme type. Denne funksjonelle strukturen gjør det mulig å komponere forekomster av disse klassene som byggesteiner.

Hvis vi ønsker å utføre to uavhengige Fremtid og gjøre noe med resultatene, kan vi bruke deretterKombiner metode som godtar en Framtid og en Funksjon med to argumenter for å behandle begge resultatene:

CompletableFuture completeableFuture = CompletableFuture.supplyAsync (() -> "Hello") .thenCombine (CompletableFuture.supplyAsync (() -> "World"), (s1, s2) -> s1 + s2)); assertEquals ("Hello World", completeableFuture.get ());

En enklere sak er når vi vil gjøre noe med to Fremtid‘Resultater, men trenger ikke å sende noen resulterende verdi ned a Framtid kjede. De thenAcceptBoth metoden er der for å hjelpe:

CompletableFuture future = CompletableFuture.supplyAsync (() -> "Hello") .thenAcceptBoth (CompletableFuture.supplyAsync (() -> "World"), (s1, s2) -> System.out.println (s1 + s2));

7. Forskjellen mellom thenApply () og deretterKomponer ()

I våre forrige seksjoner har vi vist eksempler angående thenApply () og deretterKomponer (). Begge APIene hjelper med å kjede annerledes Fullførbar fremtid samtaler, men bruken av disse to funksjonene er forskjellig.

7.1. thenApply ()

Vi kan bruke denne metoden til å jobbe med et resultat av forrige samtale. Et viktig poeng å huske er imidlertid at returtypen vil bli kombinert av alle samtaler.

Så denne metoden er nyttig når vi vil transformere resultatet av a Fullførbar fremtid anrop:

CompletableFuture finalResult = beregne (). DeretterBruk (s-> s + 1);

7.2. deretterKomponer ()

De deretterKomponer () metoden ligner på thenApply () ved at begge returnerer et nytt Fullføringsstadium. Derimot, deretterKomponer () bruker den forrige fasen som argument. Det vil flate ut og returnere a Framtid med resultatet direkte, snarere enn en nestet fremtid som vi observerte i thenApply ():

CompletableFuture computeAnother (Integer i) {return CompletableFuture.supplyAsync (() -> 10 + i); } CompletableFuture finalResult = compute (). ThenCompose (this :: computeAnother);

Så hvis ideen er å lenke Fullførbar fremtid metoder, så er det bedre å bruke deretterKomponer ().

Vær også oppmerksom på at forskjellen mellom disse to metodene er analog med forskjellen mellom kart() og flatMap ().

8. Running Multiple Fremtid parallelt

Når vi trenger å utføre flere Fremtid parallelt vil vi vanligvis vente på at alle skal utføre og deretter behandle de kombinerte resultatene.

De CompletableFuture.allOf statisk metode gjør det mulig å vente på ferdigstillelse av alle Fremtid gitt som var-arg:

CompletableFuture future1 = CompletableFuture.supplyAsync (() -> "Hei"); CompletableFuture future2 = CompletableFuture.supplyAsync (() -> "Vakker"); CompletableFuture future3 = CompletableFuture.supplyAsync (() -> "Verden"); CompletableFuture combinedFuture = CompletableFuture.allOf (future1, future2, future3); // ... combinedFuture.get (); assertTrue (future1.isDone ()); assertTrue (future2.isDone ()); assertTrue (future3.isDone ());

Legg merke til at returtypen til CompletableFuture.allOf () er en Fullførbar fremtid. Begrensningen med denne metoden er at den ikke returnerer de samlede resultatene av alle Fremtid. I stedet må vi manuelt få resultater fra Fremtid. Heldigvis, CompletableFuture.join () metode og Java 8 Streams API gjør det enkelt:

Streng kombinert = Stream.of (future1, future2, future3) .map (CompletableFuture :: join) .collect (Collectors.joining ("")); assertEquals ("Hello Beautiful World", kombinert);

De CompletableFuture.join () metoden ligner på metoden, men det kaster et ukontrollert unntak i tilfelle Framtid fullfører ikke normalt. Dette gjør det mulig å bruke den som en metodehenvisning i Stream.map () metode.

9. Håndteringsfeil

For feilhåndtering i en kjede av asynkrone beregningstrinn, må vi tilpasse kaste / fange idiom på en lignende måte.

I stedet for å fange et unntak i en syntaktisk blokk, er Fullførbar fremtid klasse lar oss håndtere det i en spesiell håndtak metode. Denne metoden mottar to parametere: et resultat av en beregning (hvis den ble fullført), og unntaket som ble kastet (hvis noen beregningstrinn ikke fullførte normalt).

I det følgende eksemplet bruker vi håndtak metode for å gi en standardverdi når den asynkrone beregningen av en hilsen ble avsluttet med en feil fordi det ikke ble gitt noe navn:

Strengnavn = null; // ... CompletableFuture completeableFuture = CompletableFuture.supplyAsync (() -> {if (name == null) {throw new RuntimeException ("Computation error!");} Return "Hello," + name;})}). håndtak ((s, t) -> s! = null? s: "Hei, fremmed!"); assertEquals ("Hello, Stranger!", completeableFuture.get ());

Anta at vi ønsker å fullføre manuelt som et alternativsscenario Framtid med en verdi, som i det første eksemplet, men har også muligheten til å fullføre den med et unntak. De fullstendigUtmerket metoden er ment for nettopp det. De completeableFuture.get () metoden i følgende eksempel kaster en Utførelse Unntak med en RuntimeException som årsak:

CompletableFuture completeableFuture = ny CompletableFuture (); // ... completeableFuture.completeExceptionally (ny RuntimeException ("Beregning mislyktes!"); // ... completeableFuture.get (); // ExecutionException

I eksemplet ovenfor kunne vi ha håndtert unntaket med håndtak metoden asynkront, men med metoden kan vi bruke den mer typiske tilnærmingen til en synkron behandling av unntak.

10. Asynkroniseringsmetoder

De fleste metoder for flytende API i Fullførbar fremtid klassen har to ekstra varianter med Asynkronisering postfix. Disse metodene er vanligvis ment for kjører et tilsvarende trinn for utføring i en annen tråd.

Metodene uten Asynkronisering postfix kjør neste utførelsesfase ved hjelp av en ringetråd. I kontrast, den Asynkronisering metoden uten Leder argument kjører et trinn ved hjelp av det vanlige gaffel / bli med pool implementering av Leder som er tilgjengelig med ForkJoinPool.commonPool () metode. Til slutt, Asynkronisering metode med en Leder argument kjører et trinn ved hjelp av bestått Leder.

Her er et modifisert eksempel som behandler resultatet av en beregning med en Funksjon forekomst. Den eneste synlige forskjellen er thenApplyAsync metoden, men under panseret er applikasjonen av en funksjon pakket inn i en ForkJoinTask forekomst (for mer informasjon om gaffel / bli med rammeverk, se artikkelen “Guide to the Fork / Join Framework in Java”). Dette gjør at vi kan parallellisere beregningen enda mer og bruke systemressurser mer effektivt:

CompletableFuture completeableFuture = CompletableFuture.supplyAsync (() -> "Hei"); CompletableFuture future = completeFuture .thenApplyAsync (s -> s + "Verden"); assertEquals ("Hello World", future.get ());

11. JDK 9 Fullførbar fremtid API

Java 9 forbedrer Fullførbar fremtid API med følgende endringer:

  • Nye fabrikkmetoder lagt til
  • Støtte for forsinkelser og tidsavbrudd
  • Forbedret støtte for underklassering

og nye instans-APIer:

  • Executor defaultExecutor ()
  • CompletableFuture newIncompleteFuture ()
  • CompletableFuture copy ()
  • CompletionStage minimalCompletionStage ()
  • Fullførbar Fremtid komplett Async (leverandørleverandør, eksekutorutfører)
  • Fullførbar Fremtid komplett Async (leverandør leverandør)
  • CompletableFuture orTimeout (lang tidsavbrudd, TimeUnit-enhet)
  • CompletableFuture completeOnTimeout (T-verdi, lang tidsavbrudd, TimeUnit-enhet)

Vi har også noen få statiske verktøy:

  • Eksekutør forsinket Eksekutør (lang forsinkelse, TimeUnit-enhet, Eksekutørutfører)
  • Eksekutør forsinket Eksekutør (lang forsinkelse, TimeUnit-enhet)
  • CompletionStage completedStage (U-verdi)
  • CompletionStage failedStage (Throwable ex)
  • CompletableFuture failedFuture (Throwable ex)

Til slutt, for å adressere timeout, har Java 9 introdusert to nye funksjoner:

  • orTimeout ()
  • completeOnTimeout ()

Her er den detaljerte artikkelen for videre lesing: Java 9 CompletableFuture API forbedringer.

12. Konklusjon

I denne artikkelen har vi beskrevet metodene og typiske bruksområder for Fullførbar fremtid klasse.

Kildekoden for artikkelen er tilgjengelig på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found