Veiledning til java.util.concurrent.Future

1. Oversikt

I denne artikkelen skal vi lære om Framtid. Et grensesnitt som har eksistert siden Java 1.5, og som kan være ganske nyttig når du arbeider med asynkrone samtaler og samtidig behandling.

2. Opprette Fremtid

Enkelt sagt, den Framtid klasse representerer et fremtidig resultat av en asynkron beregning - et resultat som til slutt vil vises i Framtid etter at behandlingen er fullført.

La oss se hvordan du skriver metoder som oppretter og returnerer a Framtid forekomst.

Langvarige metoder er gode kandidater for asynkron behandling og Framtid grensesnitt. Dette gjør at vi kan utføre en annen prosess mens vi venter på oppgaven som er innkapslet i Framtid å fullføre.

Noen eksempler på operasjoner som vil utnytte asynkroniseringen av Framtid er:

  • beregningsintensive prosesser (matematiske og vitenskapelige beregninger)
  • manipulere store datastrukturer (big data)
  • eksterne samtalemetoder (nedlasting av filer, HTML-utrangering, webtjenester).

2.1. Implementering Fremtid Med FutureTask

For vårt eksempel skal vi lage en veldig enkel klasse som beregner kvadratet til en Heltall. Dette passer definitivt ikke i kategorien "langvarig" metode, men vi kommer til å sette en Thread.sleep () ring til den for å få den til å vare 1 sekund å fullføre:

offentlig klasse SquareCalculator {private ExecutorService executor = Executors.newSingleThreadExecutor (); offentlig Fremtidsberegning (Integer input) {return executor.submit (() -> {Thread.sleep (1000); return input * input;}); }}

Bitkoden som faktisk utfører beregningen, er inneholdt i anrop() metode, levert som et lambdauttrykk. Som du kan se, er det ikke noe spesielt med det, bortsett fra søvn() samtalen nevnt tidligere.

Det blir mer interessant når vi retter oppmerksomheten mot bruken av Kan kalles og ExecutorService.

Kan kalles er et grensesnitt som representerer en oppgave som returnerer et resultat og har en enkelt anrop() metode. Her har vi opprettet en forekomst av den ved hjelp av et lambdauttrykk.

Opprette en forekomst av Kan kalles tar oss ikke med noe sted, vi må fremdeles overføre denne forekomsten til en eksekutor som vil ta seg av å starte oppgaven i en ny tråd og gi oss tilbake det verdifulle Framtid gjenstand. Det er hvor ExecutorService kommer inn.

Det er noen måter vi kan få tak i ExecutorService for eksempel er de fleste av dem levert av verktøysklasse Henrettere ‘ statiske fabrikkmetoder. I dette eksemplet har vi brukt det grunnleggende newSingleThreadExecutor (), som gir oss en ExecutorService i stand til å håndtere en enkelt tråd om gangen.

Når vi har en ExecutorService objekt, vi trenger bare å ringe sende inn() passerer vår Kan kalles som argument. sende inn() vil ta seg av å starte oppgaven og returnere a FutureTask objekt, som er en implementering av Framtid grensesnitt.

3. Forbruker Fremtid

Inntil dette har vi lært hvordan vi kan lage en forekomst av Framtid.

I denne delen lærer vi hvordan vi kan jobbe med denne forekomsten ved å utforske alle metodene som er en del av Framtid’S API.

3.1. Ved hjelp av er ferdig() og få() for å oppnå resultater

Nå må vi ringe regne ut() og bruk den returnerte Framtid for å få det resulterende Heltall. To metoder fra Framtid API vil hjelpe oss med denne oppgaven.

Future.isDone () forteller oss om utføreren er ferdig med å behandle oppgaven. Hvis oppgaven er fullført, kommer den tilbake ekte ellers kommer den tilbake falsk.

Metoden som returnerer det faktiske resultatet fra beregningen er Future.get (). Legg merke til at denne metoden blokkerer kjøringen til oppgaven er fullført, men i vårt eksempel vil dette ikke være et problem siden vi først sjekker om oppgaven er fullført ved å ringe er ferdig().

Ved å bruke disse to metodene kan vi kjøre en annen kode mens vi venter på at hovedoppgaven er ferdig:

Fremtidig fremtid = ny SquareCalculator (). Beregne (10); while (! future.isDone ()) {System.out.println ("Beregner ..."); Tråd. Søvn (300); } Heltalsresultat = future.get ();

I dette eksemplet skriver vi en enkel melding på utgangen for å fortelle brukeren at programmet utfører beregningen.

Metoden få() vil blokkere utførelsen til oppgaven er fullført. Men det trenger vi ikke bekymre oss for siden eksemplet vårt bare kommer til det punktet der få() blir ringt etter å ha forsikret deg om at oppgaven er fullført. Så i dette scenariet, future.get () kommer alltid tilbake umiddelbart.

Det er verdt å nevne det få() har en overbelastet versjon som tar en timeout og en TimeUnit som argumenter:

Heltalsresultat = future.get (500, TimeUnit.MILLISECONDS);

Forskjellen mellom få (lang, TimeUnit) og få(), er at førstnevnte vil kaste et TimeoutException hvis oppgaven ikke kommer tilbake før den angitte tidsavbruddsperioden.

3.2. Avbryte en Fremtid With Avbryt()

Anta at vi har utløst en oppgave, men av en eller annen grunn bryr vi oss ikke om resultatet lenger. Vi kan bruke Future.cancel (boolsk) å be eksekutøren om å stoppe operasjonen og avbryte den underliggende tråden:

Fremtidig fremtid = ny SquareCalculator (). Beregne (4); boolsk avbrutt = future.cancel (true);

Vår forekomst av Framtid fra koden ovenfor vil aldri fullføre driften. Faktisk, hvis vi prøver å ringe få() fra den forekomsten, etter samtalen til Avbryt(), ville resultatet være et Avbestilling Unntak. Future.isCancelled () vil fortelle oss om en Framtid var allerede kansellert. Dette kan være ganske nyttig for å unngå å få en Avbestilling Unntak.

Det er mulig at en samtale til Avbryt() mislykkes. I så fall vil den returnerte verdien være falsk. Legg merke til det Avbryt() tar en boolsk verdi som argument - dette styrer om tråden som utfører denne oppgaven skal avbrytes eller ikke.

4. Mer multithreading med Tråd Bassenger

Vår nåværende ExecutorService er enkelttrådet siden den ble oppnådd med Executors.newSingleThreadExecutor. For å markere denne “single threadness”, la oss utløse to beregninger samtidig:

SquareCalculator squareCalculator = ny SquareCalculator (); Future future1 = squareCalculator.calculate (10); Future future2 = squareCalculator.calculate (100); mens (! (future1.isDone () && future2.isDone ())) {System.out.println (String.format ("future1 er% s og future2 er% s", future1.isDone ()? "gjort": "ikke ferdig", future2.isDone ()? "gjort": "ikke ferdig")); Tråd. Søvn (300); } Heltalsresultat1 = fremtid1.get (); Heltalsresultat2 = future2.get (); System.out.println (result1 + "og" + result2); squareCalculator.shutdown ();

La oss nå analysere utdataene for denne koden:

beregning av kvadrat for: 10 framtid1 er ikke ferdig og fremtidig2 er ikke gjort framtid1 er ikke gjort og fremtidig2 er ikke gjort framtidig1 er ikke gjort og fremtidig2 er ikke gjort framtidig1 er ikke gjort og fremtidig2 er ikke ferdig beregning av kvadrat for: 100 fremtidig1 er gjort og future2 er ikke ferdig future1 er ferdig og future2 er ikke ferdig future1 er gjort og future2 er ikke ferdig 100 og 10000

Det er klart at prosessen ikke er parallell. Legg merke til hvordan den andre oppgaven bare starter når den første oppgaven er fullført, slik at hele prosessen tar rundt 2 sekunder å fullføre.

For å gjøre programmet vårt virkelig flertrådet, bør vi bruke en annen smak av ExecutorService. La oss se hvordan oppførselen til eksemplet vårt endres hvis vi bruker en trådgruppe, levert av fabrikkmetoden Executors.newFixedThreadPool ():

offentlig klasse SquareCalculator {private ExecutorService executor = Executors.newFixedThreadPool (2); // ...}

Med en enkel endring i vår SquareCalculator klasse nå har vi en eksekutor som er i stand til å bruke to samtidige tråder.

Hvis vi kjører nøyaktig samme klientkode igjen, får vi følgende utdata:

beregning av kvadrat for: 10 beregning av kvadrat for: 100 fremtid1 er ikke gjort og fremtid2 er ikke gjort fremtid 1 er ikke gjort og fremtid 2 er ikke gjort framtid 1 er ikke gjort og fremtid 2 er ikke gjort fremtid 1 er ikke gjort og fremtid 2 er ikke gjort 100 og 10000

Dette ser mye bedre ut nå. Legg merke til hvordan de to oppgavene starter og avsluttes samtidig, og hele prosessen tar rundt 1 sekund å fullføre.

Det er andre fabrikkmetoder som kan brukes til å lage trådbassenger, som Executors.newCachedThreadPool () som gjenbruker tidligere brukt Tråds når de er tilgjengelige, og Executors.newScheduledThreadPool () som planlegger kommandoer for å kjøre etter en gitt forsinkelse.

For mer informasjon om ExecutorService, les vår artikkel dedikert til emnet.

5. Oversikt over ForkJoinTask

ForkJoinTask er en abstrakt klasse som implementerer Framtid og er i stand til å kjøre et stort antall oppgaver som er vert for et lite antall faktiske tråder i ForkJoinPool.

I denne delen skal vi raskt dekke hovedegenskapene til ForkJoinPool. For en omfattende guide om emnet, sjekk vår guide til gaffelen / bli med i Java.

Da er hovedkarakteristikken til en ForkJoinTask er at det vanligvis vil gi nye deloppgaver som en del av arbeidet som kreves for å fullføre hovedoppgaven. Det genererer nye oppgaver ved å ringe gaffel() og den samler alle resultater med bli med(), dermed navnet på klassen.

Det er to abstrakte klasser som implementeres ForkJoinTask: Rekursiv oppgave som returnerer en verdi etter ferdigstillelse, og Rekursiv handling som ikke gir noe tilbake. Som navnene antyder, skal disse klassene brukes til rekursive oppgaver, som for eksempel filsystemnavigasjon eller kompleks matematisk beregning.

La oss utvide vårt forrige eksempel for å lage en klasse som, gitt en Heltall, vil beregne sumkvadratene for alle dets faktorelementer. Så hvis vi for eksempel overfører tallet 4 til kalkulatoren vår, bør vi få resultatet fra summen av 4² + 3² + 2² + 1² som er 30.

Først og fremst må vi lage en konkret implementering av Rekursiv oppgave og implementere dens beregne () metode. Det er her vi skriver forretningslogikken vår:

offentlig klasse FactorialSquareCalculator utvider RecursiveTask {private Integer n; public FactorialSquareCalculator (Integer n) {this.n = n; } @ Override-beskyttet Heltall beregne () {if (n <= 1) {return n; } FactorialSquareCalculator kalkulator = ny FactorialSquareCalculator (n - 1); kalkulator.fork (); returnere n * n + kalkulator.join (); }}

Legg merke til hvordan vi oppnår rekursivitet ved å skape en ny forekomst av FactorialSquareCalculator innenfor beregne (). Ved å ringe gaffel(), en ikke-blokkerende metode, spør vi ForkJoinPool for å starte utførelsen av denne deloppgaven.

De bli med() metoden vil returnere resultatet fra den beregningen, som vi legger til firkanten av tallet vi for øyeblikket besøker.

Nå trenger vi bare å lage en ForkJoinPool for å håndtere utførelse og trådadministrasjon:

ForkJoinPool forkJoinPool = ny ForkJoinPool (); FactorialSquareCalculator kalkulator = ny FactorialSquareCalculator (10); forkJoinPool.execute (kalkulator);

6. Konklusjon

I denne artikkelen hadde vi et helhetlig syn på Framtid grensesnitt, besøker alle metodene. Vi har også lært å utnytte kraften i trådbassenger for å utløse flere parallelle operasjoner. De viktigste metodene fra ForkJoinTask klasse, gaffel() og bli med() ble kort dekket også.

Vi har mange andre flotte artikler om parallelle og asynkrone operasjoner i Java. Her er tre av dem som er nært knyttet til Framtid grensesnitt (noen av dem er allerede nevnt i artikkelen):

  • Veiledning til Fullførbar fremtid - en implementering av Framtid med mange ekstra funksjoner introdusert i Java 8
  • Guide to the Fork / Join Framework in Java - mer om ForkJoinTask vi dekket i avsnitt 5
  • Veiledning til Java ExecutorService - viet til ExecutorService grensesnitt

Sjekk kildekoden som brukes i denne artikkelen i GitHub-depotet vårt.


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