En guide til Java ExecutorService

1. Oversikt

ExecutorService er et rammeverk gitt av JDK som forenkler utførelsen av oppgaver i asynkron modus. Generelt sett ExecutorService gir automatisk en gruppe tråder og API for å tildele oppgaver til den.

2. Instantiating ExecutorService

2.1. Fabriksmetoder for Utførere Klasse

Den enkleste måten å lage ExecutorService er å bruke en av fabrikkmetodene til Utførere klasse.

For eksempel vil følgende kodelinje opprette en tråd-pool med 10 tråder:

ExecutorService executor = Executors.newFixedThreadPool (10);

Det er flere andre fabrikkmetoder for å lage forhåndsdefinerte ExecutorService som oppfyller spesifikke brukssaker. For å finne den beste metoden for dine behov, se Oracles offisielle dokumentasjon.

2.2. Lag direkte en ExecutorService

Fordi ExecutorService er et grensesnitt, kan en forekomst av eventuelle implementeringer brukes. Det er flere implementeringer å velge mellom i java.util.concurrent pakken eller du kan lage din egen.

For eksempel ThreadPoolExecutor klasse har noen få konstruktører som kan brukes til å konfigurere en eksekutortjeneste og dens interne basseng.

ExecutorService executorService = ny ThreadPoolExecutor (1, 1, 0L, TimeUnit.MILLISECONDS, ny LinkedBlockingQueue ());

Du kan merke at koden ovenfor er veldig lik kildekoden til fabrikkmetoden newSingleThreadExecutor (). For de fleste tilfeller er en detaljert manuell konfigurasjon ikke nødvendig.

3. Tilordne oppgaver til ExecutorService

ExecutorService kan utføre Kjørbar og Kan kalles oppgaver. For å holde ting enkelt i denne artikkelen, vil to primitive oppgaver bli brukt. Legg merke til at lambdauttrykk brukes her i stedet for anonyme indre klasser:

Runnable runnableTask = () -> {prøv {TimeUnit.MILLISECONDS.sleep (300); } fange (InterruptedException e) {e.printStackTrace (); }}; Callable callableTask = () -> {TimeUnit.MILLISECONDS.sleep (300); returner "Oppgavens utførelse"; }; Liste callableTasks = ny ArrayList (); callableTasks.add (callableTask); callableTasks.add (callableTask); callableTasks.add (callableTask);

Oppgaver kan tilordnes ExecutorService ved hjelp av flere metoder, inkludert henrette(), som er arvet fra Leder grensesnitt, og også sende inn(), påkalleAny (), påkalleAll ().

De henrette() metoden er tomrom, og det gir ingen mulighet til å få resultatet av oppgavens utførelse eller å kontrollere oppgavens status (kjører den eller utføres).

executorService.execute (runnableTask);

sende inn() sender inn en Kan kalles eller a Kjørbar oppgave til en ExecutorService og returnerer et resultat av typen Framtid.

Fremtidig fremtid = executorService.submit (callableTask);

påkalleAny () tilordner en samling oppgaver til en ExecutorService, får hver til å bli henrettet, og returnerer resultatet av en vellykket gjennomføring av en oppgave (hvis det var en vellykket gjennomføring).

String result = executorService.invokeAny (callableTasks);

påkalleAll () tilordner en samling oppgaver til en ExecutorService, får hver til å bli henrettet, og returnerer resultatet av alle oppgavekjøringer i form av en liste over objekter av typen Framtid.

Liste futures = executorService.invokeAll (callableTasks);

Nå, før du går videre, må to ting til diskuteres: å slå av en ExecutorService og håndtere Framtid returtyper.

4. Slå av en ExecutorService

Generelt sett ExecutorService vil ikke automatisk bli ødelagt når det ikke er noen oppgave å behandle. Det vil holde seg i live og vente på at nytt arbeid skal gjøre.

I noen tilfeller er dette veldig nyttig; for eksempel hvis en app trenger å behandle oppgaver som vises på uregelmessig basis, eller mengden av disse oppgavene ikke er kjent på kompileringstidspunktet.

På den annen side kan en app nå sin slutt, men den blir ikke stoppet fordi den venter ExecutorService vil føre til at JVM fortsetter å kjøre.

For å slå av en ExecutorService, vi har skru av() og shutdownNow () APIer.

De skru av()metoden forårsaker ikke umiddelbar ødeleggelse av ExecutorService. Det vil gjøre ExecutorService slutte å godta nye oppgaver og slå av etter at alle løpende tråder er ferdige med sitt nåværende arbeid.

executorService.shutdown ();

De shutdownNow () metoden prøver å ødelegge ExecutorService umiddelbart, men det garanterer ikke at alle løpende trådene blir stoppet samtidig. Denne metoden returnerer en liste over oppgaver som venter på å bli behandlet. Det er opp til utvikleren å bestemme hva de skal gjøre med disse oppgavene.

Liste notExecutedTasks = executorService.shutDownNow ();

En god måte å slå av ExecutorService (som også anbefales av Oracle) er å bruke begge disse metodene kombinert med awaitTermination () metode. Med denne tilnærmingen, den ExecutorService vil først slutte å ta nye oppgaver, og deretter vente opp til en spesifisert tidsperiode på at alle oppgaver skal fullføres. Hvis tiden utløper, stoppes utførelsen umiddelbart:

executorService.shutdown (); prøv {if (! executorService.awaitTermination (800, TimeUnit.MILLISECONDS)) {executorService.shutdownNow (); }} fange (InterruptedException e) {executorService.shutdownNow (); }

5. Den Framtid Grensesnitt

De sende inn() og påkalleAll () metoder returnerer et objekt eller en samling av objekter av typen Framtid, som lar oss få resultatet av oppgavens utførelse eller å kontrollere oppgavens status (kjører eller utføres den).

De Framtid grensesnitt gir en spesiell blokkeringsmetode få() som returnerer et faktisk resultat av Kan kalles oppgavens utførelse eller null i tilfelle av Kjørbar oppgave. Ringer til få() metode mens oppgaven fortsatt kjører, vil føre til at utførelsen blokkeres til oppgaven er riktig utført og resultatet er tilgjengelig.

Fremtidig fremtid = executorService.submit (callableTask); Strengresultat = null; prøv {resultat = future.get (); } fange (InterruptedException | ExecutionException e) {e.printStackTrace (); }

Med veldig lang blokkering forårsaket av få() Metoden kan ytelsen til et program forringes. Hvis de resulterende dataene ikke er avgjørende, er det mulig å unngå et slikt problem ved å bruke tidsavbrudd:

Strengresultat = future.get (200, TimeUnit.MILLISECONDS);

Hvis utførelsesperioden er lengre enn spesifisert (i dette tilfellet 200 millisekunder), a TimeoutException vil bli kastet.

De er ferdig() metoden kan brukes til å sjekke om den tildelte oppgaven allerede er behandlet eller ikke.

De Framtid grensesnittet gir også kansellering av oppgaveutførelse med Avbryt() metode, og for å sjekke avbestillingen med isCancelled () metode:

boolsk avbrutt = future.cancel (true); boolsk isCancelled = future.isCancelled ();

6. Den PlanlagtExecutorService Grensesnitt

De PlanlagtExecutorService kjører oppgaver etter noen forhåndsdefinert forsinkelse og / eller med jevne mellomrom. Nok en gang, den beste måten å instantiere en PlanlagtExecutorService er å bruke fabrikkmetodene til Utførere klasse.

For denne delen, a PlanlagtExecutorService med en tråd vil bli brukt:

ScheduledExecutorService executorService = Eksekutører .newSingleThreadScheduledExecutor ();

For å planlegge utførelsen av en enkelt oppgave etter en fast forsinkelse, oss planlagt () metoden for PlanlagtExecutorService. Det er to planlagt () metoder som lar deg utføre Kjørbar eller Kan kalles oppgaver:

Future resultFuture = executorService.schedule (callableTask, 1, TimeUnit.SECONDS);

De planAtFixedRate () metoden kan du utføre en oppgave med jevne mellomrom etter en fast forsinkelse. Koden ovenfor forsinkes i ett sekund før den kjøres callableTask.

Følgende kodeblokk vil utføre en oppgave etter en innledende forsinkelse på 100 millisekunder, og etter det vil den utføre den samme oppgaven hver 450 millisekund. Hvis prosessoren trenger mer tid til å utføre en tildelt oppgave enn periode parameteren til planAtFixedRate () metoden, den PlanlagtExecutorService vil vente til gjeldende oppgave er fullført før du starter neste:

Future resultFuture = service .scheduleAtFixedRate (runnableTask, 100, 450, TimeUnit.MILLISECONDS);

Hvis det er nødvendig å ha en fast lengdeforsinkelse mellom gjentakelsene av oppgaven, scheduleWithFixedDelay () burde bli brukt. For eksempel vil følgende kode garantere en pause på 150 millisekunder mellom slutten av den nåværende kjøringen og starten på en annen.

service.scheduleWithFixedDelay (oppgave, 100, 150, TimeUnit.MILLISECONDS);

Ifølge planAtFixedRate () og scheduleWithFixedDelay () metodekontrakter, vil periodegjennomføring av oppgaven slutte ved avslutningen av ExecutorService eller hvis et unntak kastes under oppgavens utførelse.

7. ExecutorService vs Gaffel / Bli med

Etter utgivelsen av Java 7 bestemte mange utviklere seg for at ExecutorService rammeverk bør erstattes av gaffel / sammenføyningsrammeverket. Dette er ikke alltid den riktige avgjørelsen. Til tross for brukervennligheten og de hyppige ytelsesgevinstene forbundet med fork / join, er det også en reduksjon i mengden utviklerkontroll over samtidig kjøring.

ExecutorService gir utvikleren muligheten til å kontrollere antall genererte tråder og granulariteten til oppgaver som skal utføres av separate tråder. Den beste brukssaken for ExecutorService er behandling av uavhengige oppgaver, for eksempel transaksjoner eller forespørsler i henhold til ordningen "en tråd for en oppgave."

I motsetning til dette, ifølge Oracles dokumentasjon, var gaffel / sammenføyning designet for å øke hastigheten på arbeidet som kan brytes i mindre stykker rekursivt.

8. Konklusjon

Selv til tross for den relative enkelheten til ExecutorService, det er noen vanlige fallgruver. La oss oppsummere dem:

Holde en ubrukt ExecutorService i live: Det er en detaljert forklaring i avsnitt 4 i denne artikkelen om hvordan du stenger en ExecutorService;

Feil tråd-basseng-kapasitet mens du bruker tråd-basseng med fast lengde: Det er veldig viktig å bestemme hvor mange tråder applikasjonen trenger for å utføre oppgaver effektivt. Et trådbasseng som er for stort, vil føre til unødvendig overhead bare for å lage tråder som for det meste vil være i ventemodus. For få kan få en applikasjon til å svare på grunn av lange ventetider for oppgaver i køen;

Ringer en Framtid‘S få() metode etter oppgaveavbestilling: Et forsøk på å få resultatet av en allerede avlyst oppgave vil utløse a Avbestilling Unntak.

Uventet lang blokkering med Framtid‘S få() metode: Tidsavbrudd bør brukes for å unngå uventet venting.

Koden for denne artikkelen er tilgjengelig i et GitHub-arkiv.


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