Hvorfor har du brug for Executor-grænsefladen?

Før Java 5 skulle du skrive al din egen kodetrådshåndtering i din applikation. Desuden skabes enny trådobjekt er en ressourcekrævende operation, og det giver ikke mening at oprette en ny tråd til hver letvægtsopgave. Og fordi dette problem er kendt for absolut alle udviklere af multi-threaded-applikationer, besluttede de at bringe denne funktionalitet ind i Java som Executor- rammeværket.

Hvad er den store idé? Det er enkelt: I stedet for at oprette en ny tråd for hver ny opgave, opbevares tråde i en slags "lager", og når der kommer en ny opgave, henter vi en eksisterende tråd frem i stedet for at oprette en ny.

De vigtigste grænseflader i denne ramme er Executor , ExecutorService og ScheduledExecutorService , som hver udvider funktionaliteten af ​​den forrige.

Executor-grænsefladen er basisgrænsefladen. Den erklærer en enkelt void execute (Kørbar kommando) metode, der er implementeret af et Kørbart objekt.

ExecutorService - grænsefladen er mere interessant. Det har metoder til at styre færdiggørelsen af ​​arbejdet, samt metoder til at returnere en form for resultat. Lad os se nærmere på dens metoder:

Metode Beskrivelse
void shutdown(); Kaldning af denne metode stopper ExecutorService . Alle opgaver, der allerede er sendt til behandling, vil blive gennemført, men nye opgaver vil ikke blive accepteret.
List<Runnable> shutdownNow();

Kaldning af denne metode stopper ExecutorService . Thread.interrupt vil blive kaldt for alle opgaver, der allerede er sendt til behandling. Denne metode returnerer en liste over opgaver i kø.

Metoden venter ikke på afslutningen af ​​alle opgaver, der er "i gang" på det tidspunkt, metoden kaldes.

Advarsel: Kaldning af denne metode kan lække ressourcer.

boolesk isShutdown(); Kontrollerer om ExecutorService er stoppet.
boolesk erTerminated(); Returnerer sand, hvis alle opgaver blev udført efter nedlukning af ExecutorService . Indtil shutdown() eller shutdownNow() kaldes, vil det altid returnere false .
boolean awaitTermination (lang timeout, TimeUnit enhed) kaster InterruptedException;

Efter at shutdown() metoden er kaldt, blokerer denne metode tråden, som den kaldes på, indtil en af ​​følgende betingelser er sand:

  • alle planlagte opgaver er afsluttet;
  • den timeout, der er overført til metoden, er udløbet;
  • den aktuelle tråd afbrydes.

Returnerer sand , hvis alle opgaver er fuldført, og falsk , hvis timeout udløber før afslutning.

<T> Fremtidig<T> indsend(Kallbar<T> opgave);

Tilføjer en Callable- opgave til ExecutorService og returnerer et objekt, der implementerer Future- grænsefladen.

<T> er typen af ​​resultatet af den beståede opgave.

<T> Fremtidig<T> indsend(Kørbar opgave, T-resultat);

Tilføjer en Runnable- opgave til ExecutorService og returnerer et objekt, der implementerer Future- grænsefladen.

T -resultatparameteren er det, der returneres af et kald til get() -metoden på resultatetFremtidigt objekt.

Future<?> submit(Kørbar opgave);

Tilføjer en Runnable- opgave til ExecutorService og returnerer et objekt, der implementerer Future- grænsefladen.

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

<T> List<Future<T>> invokeAll(Collection<? udvider Callable<T>> opgaver) kaster InterruptedException;

Sender en liste over kaldbare opgaver til ExecutorService . Returnerer en liste over Futures, hvorfra vi kan få resultatet af arbejdet. Denne liste returneres, når alle indsendte opgaver er udført.

Hvis opgavesamlingen ændres, mens metoden kører, er resultatet af denne metode udefineret.

<T> List<Future<T>> invokeAll(Collection<? udvider Callable<T>> opgaver, lang timeout, TimeUnit unit) kaster InterruptedException;

Sender en liste over kaldbare opgaver til ExecutorService . Returnerer en liste over Futures, hvorfra vi kan få resultatet af arbejdet. Denne liste returneres, når alle beståede opgaver er fuldført, eller efter den timeout, der er overført til metoden, er udløbet, alt efter hvad der kommer først.

Hvis timeout udløber, annulleres ufærdige opgaver.

Bemærk: Det er muligt, at en annulleret opgave ikke stopper med at køre (vi vil se denne bivirkning i eksemplet).

Hvis opgavesamlingen ændres, mens metoden kører, er resultatet af denne metode udefineret.

<T> T invokeAny(Collection<? udvider Callable<T>> opgaver) kaster InterruptedException, ExecutionException;

Sender en liste over kaldbare opgaver til ExecutorService . Returnerer resultatet af en af ​​opgaverne (hvis nogen), der blev fuldført uden at afgive en undtagelse (hvis nogen).

Hvis opgavesamlingen ændres, mens metoden kører, er resultatet af denne metode udefineret.

<T> T invokeAny(Collection<? udvider Callable<T>> opgaver, lang timeout, TimeUnit enhed) kaster InterruptedException, ExecutionException, TimeoutException;

Sender en liste over kaldbare opgaver til ExecutorService . Returnerer resultatet af en af ​​opgaverne (hvis nogen), der blev fuldført uden at afgive en undtagelse, før den timeout, der blev overført til metoden, er udløbet.

Hvis opgavesamlingen ændres, mens metoden kører, er resultatet af denne metode udefineret.

Lad os se på et lille eksempel på at arbejde 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;
       }
   }
}

Produktion:

færdig
udført
Futures modtaget
søvn 1: søvn afbrudt
søvn 1: søvn afbrudt færdig
gjort sandt sandt


Hver opgave kører i 5 sekunder. Vi skabte en pulje til to tråde, så de første to outputlinjer giver perfekt mening.

Seks sekunder efter programmets start, ophører invokeAll- metoden, og resultatet returneres som en liste over Futures . Dette kan ses fra output-strengen Futures modtaget .

Når de første to opgaver er udført, begynder to mere. Men fordi den timeout, der er angivet i invokeAll -metoden, udløber, har disse to opgaver ikke tid til at fuldføre. De modtager en "annuller" -kommando. Det er derfor, udgangen har to linjer med dvale 1: dvale afbrudt .

Og så kan du se to linjer mere med færdig . Dette er den bivirkning, som jeg nævnte, da jeg beskrev invokeAll - metoden.

Den femte og sidste opgave kommer aldrig i gang, så det ser vi ikke noget om i outputtet.

De sidste to linjer er resultatet af at kalde metoderne isShutdown og isTerminated .

Det er også interessant at køre dette eksempel i debug-tilstand og se på opgavestatus efter timeout er udløbet (indstil et breakpoint på linjen med executorService.shutdown(); ):

Vi ser, at to opgaver fuldført normalt , og tre opgaver blev "Annulleret" .

ScheduledExecutorService

For at afslutte vores diskussion af eksekutører, lad os tage et kig på ScheduledExecutorService .

Det har 4 metoder:

Metode Beskrivelse
public ScheduledFuture<?>-skema(Kørbar kommando, lang forsinkelse, TimeUnit-enhed); Planlægger den beståede Runnable- opgave til at køre én gang efter den forsinkelse, der er angivet som et argument.
offentlig <V> ScheduledFuture<V> tidsplan (Opkaldbar<V> opkaldbar, lang forsinkelse, TimeUnit enhed); Planlægger den beståede Callable- opgave til at køre én gang efter den forsinkelse, der er angivet som et argument.
public ScheduledFuture<?> scheduleAtFixedRate(Kørbar kommando, lang initialDelay, lang periode, TimeUnit enhed); Planlægger periodisk udførelse af den beståede opgave, som vil blive udført for første gang efter initialDelay , og hver efterfølgende kørsel begynder efter periode .
public ScheduledFuture<?> scheduleWithFixedDelay(Kørbar kommando, lang initialDelay, lang forsinkelse, TimeUnit enhed); Planlægger periodisk udførelse af den beståede opgave, som vil blive udført for første gang efter initialDelay , og hver efterfølgende kørsel vil begynde efter forsinkelse (perioden mellem afslutningen af ​​den forrige kørsel og starten af ​​den nuværende).