Hvorfor trenger du Executor-grensesnittet?

Før Java 5 måtte du skrive all din egen kodetrådhåndtering i applikasjonen din. I tillegg oppretter du enny trådobjekt er en ressurskrevende operasjon, og det gir ikke mening å lage en ny tråd for hver lett oppgave. Og fordi dette problemet er kjent for absolutt alle utviklere av flertrådede applikasjoner, bestemte de seg for å bringe denne funksjonaliteten inn i Java som Executor- rammeverket.

Hva er den store ideen? Det er enkelt: i stedet for å lage en ny tråd for hver ny oppgave, holdes tråder i et slags «lager», og når en ny oppgave kommer, henter vi en eksisterende tråd i stedet for å lage en ny.

Hovedgrensesnittene til dette rammeverket er Executor , ExecutorService og ScheduledExecutorService , som hver utvider funksjonaliteten til den forrige.

Executor-grensesnittet er basisgrensesnittet. Den erklærer en enkelt void execute (kjørbar kommando) metode som er implementert av et kjørbart objekt.

ExecutorService - grensesnittet er mer interessant. Den har metoder for å administrere fullføringen av arbeid, samt metoder for å returnere en eller annen form for resultat. La oss se nærmere på metodene:

Metode Beskrivelse
void shutdown(); Å kalle denne metoden stopper ExecutorService . Alle oppgaver som allerede er sendt inn til behandling vil bli utført, men nye oppgaver vil ikke bli akseptert.
List<Runnable> shutdownNow();

Å kalle denne metoden stopper ExecutorService . Thread.interrupt vil kalles opp for alle oppgaver som allerede er sendt inn for behandling. Denne metoden returnerer en liste over oppgaver i kø.

Metoden venter ikke på fullføring av alle oppgaver som "pågår" på tidspunktet metoden kalles.

Advarsel: Å kalle denne metoden kan lekke ressurser.

boolsk isShutdown(); Sjekker om ExecutorService er stoppet.
boolsk erTerminated(); Returnerer sann hvis alle oppgaver ble fullført etter avslutning av ExecutorService . Inntil shutdown() eller shutdownNow() kalles, vil den alltid returnere false .
boolean awaitTermination (lang tidsavbrudd, TimeUnit-enhet) kaster InterruptedException;

Etter at shutdown()- metoden er kalt, blokkerer denne metoden tråden den kalles på, inntil en av følgende betingelser er oppfylt:

  • alle planlagte oppgaver er fullført;
  • tidsavbruddet som ble sendt til metoden har gått ut;
  • gjeldende tråd er avbrutt.

Returnerer sann hvis alle oppgaver er fullført, og usann hvis tidsavbruddet går før avslutning.

<T> Future<T> submit(Callable<T> task);

Legger til en Callable- oppgave til ExecutorService og returnerer et objekt som implementerer Future- grensesnittet.

<T> er typen resultat av den beståtte oppgaven.

<T> Future<T> submit(Kjørbar oppgave, T-resultat);

Legger til en kjørbar oppgave til ExecutorService og returnerer et objekt som implementerer Future- grensesnittet.

T -resultatparameteren er det som blir returnert av et kall til get() -metoden på resultatetFremtidig objekt.

Future<?> submit(Kjørbar oppgave);

Legger til en kjørbar oppgave til ExecutorService og returnerer et objekt som implementerer Future- grensesnittet.

Hvis vi kaller get() -metoden på det resulterende Future- objektet, får vi null.

<T> List<Future<T>> invokeAll(Samling<? utvider Callable<T>> oppgaver) kaster InterruptedException;

Sender en liste over oppringbare oppgaver til ExecutorService . Returnerer en liste over Futures som vi kan få resultatet av arbeidet fra. Denne listen returneres når alle innsendte oppgaver er fullført.

Hvis oppgavesamlingen endres mens metoden kjører, er resultatet av denne metoden udefinert.

<T> List<Future<T>> invokeAll(Samling<? utvider Callable<T>> oppgaver, lang tidsavbrudd, TimeUnit-enhet) kaster InterruptedException;

Sender en liste over oppringbare oppgaver til ExecutorService . Returnerer en liste over Futures som vi kan få resultatet av arbeidet fra. Denne listen returneres når alle beståtte oppgaver er fullført, eller etter at tidsavbruddet som er gitt til metoden er utløpt, avhengig av hva som kommer først.

Hvis tidsavbruddet går ut, avbrytes uferdige oppgaver.

Merk: Det er mulig at en avbrutt oppgave ikke slutter å kjøre (vi vil se denne bivirkningen i eksemplet).

Hvis oppgavesamlingen endres mens metoden kjører, er resultatet av denne metoden udefinert.

<T> T invokeAny(Collection<? utvider Callable<T>> oppgaver) kaster InterruptedException, ExecutionException;

Sender en liste over oppringbare oppgaver til ExecutorService . Returnerer resultatet av en av oppgavene (hvis noen) som ble fullført uten å kaste et unntak (hvis noen).

Hvis oppgavesamlingen endres mens metoden kjører, er resultatet av denne metoden udefinert.

<T> T invokeAny(Collection<? utvider Callable<T>> oppgaver, lang tidsavbrudd, TimeUnit-enhet) kaster InterruptedException, ExecutionException, TimeoutException;

Sender en liste over oppringbare oppgaver til ExecutorService . Returnerer resultatet av en av oppgavene (hvis noen) som ble fullført uten å kaste et unntak før tidsavbruddet som ble sendt til metoden, har utløpt.

Hvis oppgavesamlingen endres mens metoden kjører, er resultatet av denne metoden udefinert.

La oss se på et lite eksempel på arbeid med ExecutorService .


import java.util.List;
import java.util.concurrent.*;

public class ExecutorServiceTest {
   public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
//Create an ExecutorService for 2 threads
       java.util.concurrent.ExecutorService executorService = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
// Create 5 tasks
       MyRunnable task1 = new MyRunnable();
       MyRunnable task2 = new MyRunnable();
       MyRunnable task3 = new MyRunnable();
       MyRunnable task4 = new MyRunnable();
       MyRunnable task5 = new MyRunnable();

       final List<MyRunnable> tasks = List.of(task1, task2, task3, task4, task5);
// Pass a list that contains the 5 tasks we created
       final List<Future<Void>> futures = executorService.invokeAll(tasks, 6, TimeUnit.SECONDS);
       System.out.println("Futures received");

// Stop the ExecutorService
       executorService.shutdown();

       try {
           TimeUnit.SECONDS.sleep(3);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }

       System.out.println(executorService.isShutdown());
       System.out.println(executorService.isTerminated());
   }

   public static class MyRunnable implements Callable<Void> {

       @Override
       public void call() {
// Add 2 delays. When the ExecutorService is stopped, we will see which delay is in progress when the attempt is made to stop execution of the task
           try {
               TimeUnit.SECONDS.sleep(3);
           } catch (InterruptedException e) {
               System.out.println("sleep 1: " + e.getMessage());
           }
           try {
               TimeUnit.SECONDS.sleep(2);
           } catch (InterruptedException e) {
               System.out.println("sleep 2: " + e.getMessage());
           }
           System.out.println("done");
           return null;
       }
   }
}

Produksjon:

ferdig
gjort
Futures mottatt
søvn 1: søvn avbrutt
søvn 1: søvn avbrutt
ferdig
gjort
sant
sant

Hver oppgave varer i 5 sekunder. Vi laget et basseng for to tråder, så de to første linjene med utdata gir perfekt mening.

Seks sekunder etter at programmet starter, blir invokeAll- metoden tidsavbrutt, og resultatet returneres som en liste over Futures . Dette kan sees fra utdatastrengen Futures receives .

Etter at de to første oppgavene er gjort, begynner to til. Men fordi tidsavbruddet satt i invokeAll -metoden utløper, har ikke disse to oppgavene tid til å fullføre. De mottar en "avbryt" -kommando. Det er derfor utgangen har to linjer med dvale 1: dvale avbrutt .

Og så kan du se to linjer til med ferdig . Dette er bivirkningen jeg nevnte når jeg beskrev invokeAll- metoden.

Den femte og siste oppgaven kommer aldri i gang, så vi ser ikke noe om det i utdataene.

De to siste linjene er resultatet av å kalle metodene isShutdown og isTerminated .

Det er også interessant å kjøre dette eksemplet i feilsøkingsmodus og se på oppgavestatusen etter at tidsavbruddet har gått (sett et bruddpunkt på linjen med executorService.shutdown(); ):

Vi ser at to oppgaver Fullført normalt , og tre oppgaver ble "Avbrutt" .

ScheduledExecutorService

For å avslutte vår diskusjon om eksekutører, la oss ta en titt på ScheduledExecutorService .

Den har 4 metoder:

Metode Beskrivelse
offentlig ScheduledFuture<?>-plan (Kjørbar kommando, lang forsinkelse, TimeUnit-enhet); Planlegger den beståtte Runnable- oppgaven til å kjøre én gang etter forsinkelsen angitt som et argument.
offentlig <V> ScheduledFuture<V> tidsplan(Callable<V> callable, long delay, TimeUnit unit); Planlegger den beståtte Callable- oppgaven til å kjøre én gang etter forsinkelsen angitt som et argument.
public ScheduledFuture<?> scheduleAtFixedRate(Kjørbar kommando, lang initialDelay, lang periode, TimeUnit-enhet); Planlegger periodisk utførelse av den beståtte oppgaven, som vil bli utført for første gang etter initialDelay , og hver påfølgende kjøring vil begynne etter periode .
public ScheduledFuture<?> scheduleWithFixedDelay(Kjørbar kommando, lang initialDelay, lang forsinkelse, TimeUnit-enhet); Planlegger periodisk utførelse av den beståtte oppgaven, som vil bli utført for første gang etter initialDelay , og hver påfølgende kjøring vil begynne etter forsinkelse (perioden mellom fullføringen av forrige kjøring og starten av den nåværende).