En guide til Java ExecutorService

1. Oversigt

ExecutorService er en ramme leveret af JDK, som forenkler udførelsen af ​​opgaver i asynkron tilstand. Generelt sagt, ExecutorService giver automatisk en pulje af tråde og API til at tildele opgaver til den.

2. Instantierende ExecutorService

2.1. Fabriksmetoder for Eksekutører Klasse

Den nemmeste måde at oprette på ExecutorService er at bruge en af ​​fabriksmetoderne til Eksekutører klasse.

For eksempel opretter følgende linje kode en trådpulje med 10 tråde:

ExecutorService eksekutor = Executors.newFixedThreadPool (10);

Der er flere andre fabriksmetoder til at skabe foruddefineret ExecutorService der opfylder specifikke brugssager. Se Oracles officielle dokumentation for at finde den bedste metode til dine behov.

2.2. Opret direkte en ExecutorService

Fordi ExecutorService er en grænseflade, en instans af dens implementeringer kan bruges. Der er flere implementeringer at vælge imellem i java.util.concurrent pakke, eller du kan oprette din egen.

F.eks ThreadPoolExecutor klasse har et par konstruktører, som kan bruges til at konfigurere en eksekutortjeneste og dens interne pool.

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

Du bemærker muligvis, at koden ovenfor ligner meget kildekoden fra fabriksmetoden newSingleThreadExecutor (). I de fleste tilfælde er en detaljeret manuel konfiguration ikke nødvendig.

3. Tildeling af opgaver til ExecutorService

ExecutorService kan udføre Kan køres og Kan kaldes opgaver. For at holde tingene enkle i denne artikel vil to primitive opgaver blive brugt. Bemærk, at lambda-udtryk bruges her i stedet for anonyme indre klasser:

Runnable runnableTask = () -> {prøv {TimeUnit.MILLISECONDS.sleep (300); } fange (InterruptedException e) {e.printStackTrace (); }}; Kan kaldes callableTask = () -> {TimeUnit.MILLISECONDS.sleep (300); returnere "Opgavens udførelse"; }; Liste callableTasks = ny ArrayList (); callableTasks.add (callableTask); callableTasks.add (callableTask); callableTasks.add (callableTask);

Opgaver kan tildeles til ExecutorService ved hjælp af flere metoder, herunder udføre (), som er arvet fra Eksekutor interface, og også Indsend(), invokeAny (), invokeAll ().

Det udføre () metode er ugyldig, og det giver ingen mulighed for at få resultatet af opgavens udførelse eller til at kontrollere opgavens status (kører eller udføres den).

executorService.execute (runnableTask);

Indsend() indsender en Kan kaldes eller a Kan køres opgave til en ExecutorService og returnerer et resultat af typen Fremtid.

Fremtidig fremtid = executorService.submit (callableTask);

påkalde enhver () tildeler en samling af opgaver til en ExecutorService, får hver til at blive udført, og returnerer resultatet af en vellykket udførelse af en opgave (hvis der var en vellykket udførelse).

String result = executorService.invokeAny (callableTasks);

påkaldeAlle () tildeler en samling af opgaver til en ExecutorService, får hver til at blive udført, og returnerer resultatet af alle opgaveudførelser i form af en liste over objekter af typen Fremtid.

Liste futures = executorService.invokeAll (callableTasks);

Nu, inden vi går videre, skal to yderligere ting diskuteres: nedlukning af en ExecutorService og håndtere Fremtid returneringstyper.

4. Lukning af en ExecutorService

Generelt er det ExecutorService ødelægges ikke automatisk, når der ikke er nogen opgave at behandle. Det vil forblive i live og vente på, at nyt arbejde skal udføres.

I nogle tilfælde er dette meget nyttigt; for eksempel, hvis en app har brug for at behandle opgaver, der vises på uregelmæssig basis, eller mængden af ​​disse opgaver ikke vides på kompileringstidspunktet.

På den anden side kan en app nå sin slutning, men den stoppes ikke, fordi den venter ExecutorService vil få JVM til at køre.

For at lukke en ExecutorService, vi har lukke ned() og shutdownNow () API'er.

Det lukke ned()Metoden forårsager ikke øjeblikkelig ødelæggelse af ExecutorService. Det vil gøre ExecutorService stop med at acceptere nye opgaver og luk ned, når alle kørende tråde er færdige med deres nuværende arbejde.

executorService.shutdown ();

Det shutdownNow () metoden forsøger at ødelægge ExecutorService straks, men det garanterer ikke, at alle kørende tråde stoppes på samme tid. Denne metode returnerer en liste over opgaver, der venter på at blive behandlet. Det er op til udvikleren at beslutte, hvad de skal gøre med disse opgaver.

Liste notExecutedTasks = executorService.shutDownNow ();

En god måde at lukke ExecutorService (hvilket også anbefales af Oracle) er at bruge begge disse metoder kombineret med awaitTermination () metode. Med denne tilgang er den ExecutorService stopper først med at tage nye opgaver og venter derefter op til et bestemt tidsrum, indtil alle opgaver skal udføres. Hvis denne tid udløber, stoppes udførelsen med det samme:

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

5. Den Fremtid Interface

Det Indsend() og påkaldeAlle () metoder returnerer et objekt eller en samling objekter af typen Fremtid, som giver os mulighed for at få resultatet af en opgaves udførelse eller kontrollere opgavens status (kører eller udføres den).

Det Fremtid interface giver en særlig blokeringsmetode få() som returnerer et faktisk resultat af Kan kaldes opgavens udførelse eller nul i tilfælde af Kan køres opgave. Ringer til få() metode, mens opgaven stadig kører, får eksekveringen til at blokere, indtil opgaven udføres korrekt, og resultatet er tilgængeligt.

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

Med meget lang blokering forårsaget af få() metode, kan en applikations præstation forringes. Hvis de resulterende data ikke er afgørende, er det muligt at undgå et sådant problem ved at bruge timeouts:

String result = future.get (200, TimeUnit.MILLISECONDS);

Hvis udførelsesperioden er længere end angivet (i dette tilfælde 200 millisekunder), a TimeoutException vil blive kastet.

Det Er gjort() metode kan bruges til at kontrollere, om den tildelte opgave allerede er behandlet eller ej.

Det Fremtid interface giver også mulighed for annullering af opgaveudførelse med afbestille() metode og kontrollere aflysningen med isCancelled () metode:

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

6. Den ScheduledExecutorService Interface

Det ScheduledExecutorService kører opgaver efter en foruddefineret forsinkelse og / eller med jævne mellemrum. Endnu en gang den bedste måde at instantiere en ScheduledExecutorService er at bruge fabriksmetoderne til Eksekutører klasse.

For dette afsnit, a ScheduledExecutorService med en tråd vil blive brugt:

ScheduledExecutorService executorService = Eksekutører .newSingleThreadScheduledExecutor ();

For at planlægge udførelsen af ​​en enkelt opgave efter en fast forsinkelse, skal vi planlagt () metode til ScheduledExecutorService. Der er to planlagt () metoder, der giver dig mulighed for at udføre Kan køres eller Kan kaldes opgaver:

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

Det scheduleAtFixedRate () metode lader udføre en opgave med jævne mellemrum efter en fast forsinkelse. Koden ovenfor forsinkes i et sekund, før den udføres callableTask.

Den følgende kodeblok udfører en opgave efter en indledende forsinkelse på 100 millisekunder, og derefter udfører den den samme opgave hver 450 millisekunder. Hvis processoren har brug for mere tid til at udføre en tildelt opgave end periode parameter for scheduleAtFixedRate () metode, den ScheduledExecutorService venter, indtil den aktuelle opgave er afsluttet, inden den næste starter:

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

Hvis det er nødvendigt at have en fast længde forsinkelse mellem gentagelser af opgaven, scheduleWithFixedDelay () skal bruges. For eksempel garanterer følgende kode en pause på 150 millisekunder mellem afslutningen af ​​den aktuelle udførelse og starten af ​​en anden.

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

Ifølge scheduleAtFixedRate () og scheduleWithFixedDelay () Metodekontrakter slutter udførelsen af ​​opgaven ved afslutningen af ​​programmet ExecutorService eller hvis en undtagelse kastes under udførelse af opgaven.

7. ExecutorService vs Fork / Deltag

Efter frigivelsen af ​​Java 7 besluttede mange udviklere, at ExecutorService ramme bør erstattes af gaffel / sammenføjningsrammen. Dette er dog ikke altid den rigtige beslutning. På trods af enkelheden i brugen og de hyppige præstationsgevinster forbundet med fork / join er der også en reduktion i mængden af ​​udviklerkontrol over samtidig udførelse.

ExecutorService giver udvikleren mulighed for at styre antallet af genererede tråde og granulariteten af ​​opgaver, der skal udføres af separate tråde. Den bedste brugssag til ExecutorService er behandling af uafhængige opgaver, såsom transaktioner eller anmodninger i henhold til ordningen "en tråd til en opgave."

I modsætning hertil blev gaffel / sammenføjning ifølge Oracles dokumentation designet til at fremskynde arbejde, der kan opdeles i mindre stykker rekursivt.

8. Konklusion

Selv på trods af ExecutorService, der er et par almindelige faldgruber. Lad os sammenfatte dem:

Opbevaring ubrugt ExecutorService i live: Der er en detaljeret forklaring i afsnit 4 i denne artikel om, hvordan man lukker en ExecutorService;

Forkert tråd-pool kapacitet under brug af tråd-pool med fast længde: Det er meget vigtigt at bestemme, hvor mange tråde applikationen har brug for for at udføre opgaver effektivt. En trådpulje, der er for stor, forårsager unødvendige omkostninger bare for at oprette tråde, som for det meste vil være i ventetilstand. For få kan få en applikation til at reagere ikke på grund af lange ventetider for opgaver i køen;

Opkald til en Fremtid'S få() metode efter annullering af opgave: Et forsøg på at få resultatet af en allerede annulleret opgave vil udløse en Annullering Undtagelse.

Uventet lang blokering med Fremtid'S få() metode: Timeouts bør bruges til at undgå uventede ventetider.

Koden til denne artikel er tilgængelig i et GitHub-arkiv.


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