Waarom heb je de Executor-interface nodig?

Vóór Java 5 moest u al uw eigen codethreadbeheer in uw toepassing schrijven. Daarnaast het creëren van eennieuwe Draadobject is een resource-intensieve bewerking en het heeft geen zin om voor elke lichte taak een nieuwe thread te maken. En omdat dit probleem bekend is bij absoluut elke ontwikkelaar van multi-threaded applicaties, besloten ze om deze functionaliteit in Java te brengen als het Executor- framework.

Wat is het grote idee? Het is eenvoudig: in plaats van een nieuwe thread te maken voor elke nieuwe taak, worden threads bewaard in een soort "opslag", en wanneer een nieuwe taak arriveert, halen we een bestaande thread op in plaats van een nieuwe te maken.

De belangrijkste interfaces van dit framework zijn Executor , ExecutorService en ScheduledExecutorService , die elk de functionaliteit van de vorige uitbreiden.

De Executor-interface is de basisinterface. Het declareert een enkele ongeldige execute (Runnable-opdracht) methode die wordt geïmplementeerd door een Runnable- object.

De interface van ExecutorService is interessanter. Het heeft methoden om de voltooiing van werk te beheren, evenals methoden om een ​​bepaald resultaat terug te geven. Laten we de methoden eens nader bekijken:

Methode Beschrijving
nietig afsluiten(); Het aanroepen van deze methode stopt de ExecutorService . Alle taken die al ter verwerking zijn ingediend, worden voltooid, maar nieuwe taken worden niet geaccepteerd.
Lijst<Runnable> shutdownNow();

Het aanroepen van deze methode stopt de ExecutorService . Thread.interrupt wordt aangeroepen voor alle taken die al ter verwerking zijn ingediend. Deze methode retourneert een lijst met taken in de wachtrij.

De methode wacht niet op de voltooiing van alle taken die "in uitvoering" zijn op het moment dat de methode wordt aangeroepen.

Waarschuwing: het aanroepen van deze methode kan bronnen lekken.

boolean isShutdown(); Controleert of de ExecutorService is gestopt.
boolean isTerminated(); Retourneert true als alle taken zijn voltooid na het afsluiten van de ExecutorService . Totdat shutdown() of shutdownNow() wordt aangeroepen, zal het altijd false retourneren .
boolean waitTermination (lange time-out, TimeUnit-eenheid) genereert InterruptedException;

Nadat de methode shutdown() is aangeroepen, blokkeert deze methode de thread waarop deze wordt aangeroepen, totdat aan een van de volgende voorwaarden wordt voldaan:

  • alle geplande taken zijn voltooid;
  • de time-out die is doorgegeven aan de methode is verstreken;
  • de huidige thread wordt onderbroken.

Retourneert true als alle taken zijn voltooid en false als de time-out verstrijkt voordat deze wordt beëindigd.

<T> Toekomst<T> indienen(Callable<T> taak);

Voegt een Callable- taak toe aan de ExecutorService en retourneert een object dat de Future- interface implementeert .

<T> is het type resultaat van de geslaagde taak.

<T> Toekomst<T> indienen (Uitvoerbare taak, T-resultaat);

Voegt een uitvoerbare taak toe aan de ExecutorService en retourneert een object dat de Future- interface implementeert.

De resultaatparameter T is wat wordt geretourneerd door een aanroep van de methode get() op het resultaatToekomstig voorwerp.

Toekomst <?> indienen (uitvoerbare taak);

Voegt een uitvoerbare taak toe aan de ExecutorService en retourneert een object dat de Future- interface implementeert.

Als we de methode get() aanroepen op het resulterende Future- object, krijgen we null.

<T> List<Future<T>> invokeAll(Collection<? breidt Callable<T>> taken uit) gooit InterruptedException;

Geeft een lijst met opvraagbare taken door aan de ExecutorService . Retourneert een lijst met Futures waaruit we het resultaat van het werk kunnen halen. Deze lijst wordt geretourneerd wanneer alle ingediende taken zijn voltooid.

Als de verzameling taken wordt gewijzigd terwijl de methode wordt uitgevoerd, is het resultaat van deze methode ongedefinieerd.

<T> List<Future<T>> invokeAll(Collection<? breidt Callable<T>> taken uit, lange time-out, TimeUnit-eenheid) gooit InterruptedException;

Geeft een lijst met opvraagbare taken door aan de ExecutorService . Retourneert een lijst met Futures waaruit we het resultaat van het werk kunnen halen. Deze lijst wordt geretourneerd wanneer alle voltooide taken zijn voltooid, of nadat de time-out die aan de methode is doorgegeven, is verstreken, afhankelijk van wat zich het eerst voordoet.

Als de time-out verstrijkt, worden onvoltooide taken geannuleerd.

Opmerking: het is mogelijk dat een geannuleerde taak niet stopt met uitvoeren (we zullen deze bijwerking zien in het voorbeeld).

Als de verzameling taken wordt gewijzigd terwijl de methode wordt uitgevoerd, is het resultaat van deze methode ongedefinieerd.

<T> T invokeAny(Collection<? breidt Callable<T>> taken uit) gooit InterruptedException, ExecutionException;

Geeft een lijst met opvraagbare taken door aan de ExecutorService . Retourneert het resultaat van een van de taken (indien aanwezig) die is voltooid zonder een uitzondering te genereren (indien aanwezig).

Als de verzameling taken wordt gewijzigd terwijl de methode wordt uitgevoerd, is het resultaat van deze methode ongedefinieerd.

<T> T invokeAny(Collection<? breidt Callable<T>> taken uit, lange time-out, TimeUnit-eenheid) gooit InterruptedException, ExecutionException, TimeoutException;

Geeft een lijst met opvraagbare taken door aan de ExecutorService . Retourneert het resultaat van een van de taken (indien van toepassing) die is voltooid zonder een uitzondering te genereren voordat de time-out die aan de methode is doorgegeven, is verstreken.

Als de verzameling taken wordt gewijzigd terwijl de methode wordt uitgevoerd, is het resultaat van deze methode ongedefinieerd.

Laten we eens kijken naar een klein voorbeeld van werken met 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;
       }
   }
}

Uitgang:

done
done
Futures ontvangen
slaap 1: slaap onderbroken
slaap 1: slaap onderbroken
gedaan
gedaan waar
waar

Elke taak duurt 5 seconden. We hebben een pool gemaakt voor twee threads, dus de eerste twee uitvoerregels zijn volkomen logisch.

Zes seconden nadat het programma is gestart, treedt er een time-out op voor de methode invokeAll en wordt het resultaat geretourneerd als een lijst met Futures . Dit is af te lezen aan de uitvoerreeks Ontvangen Futures .

Nadat de eerste twee taken zijn voltooid, beginnen er nog twee. Maar omdat de time-out die is ingesteld in de methode invokeAll verstrijkt, hebben deze twee taken geen tijd om te voltooien. Ze krijgen een commando "annuleren" . Daarom heeft de uitvoer twee regels met slaap 1: slaap onderbroken .

En dan zie je nog twee regels met done . Dit is het neveneffect dat ik noemde bij het beschrijven van de invokeAll- methode.

De vijfde en laatste taak wordt niet eens gestart, dus we zien er niets over in de uitvoer.

De laatste twee regels zijn het resultaat van het aanroepen van de methoden isShutdown en isTerminated .

Het is ook interessant om dit voorbeeld in foutopsporingsmodus uit te voeren en naar de taakstatus te kijken nadat de time-out is verstreken (stel een breekpunt in op de lijn met executorService.shutdown(); ):

We zien dat twee taken normaal zijn voltooid en drie taken zijn "geannuleerd" .

ScheduledExecutorService

Laten we, om onze bespreking van uitvoerders af te sluiten, eens kijken naar ScheduledExecutorService .

Het heeft 4 methoden:

Methode Beschrijving
public ScheduledFuture<?> schedule(uitvoerbaar commando, lange vertraging, TimeUnit-eenheid); Plan de uitgevoerde uitvoerbare taak om één keer te worden uitgevoerd na de vertraging die als argument is opgegeven.
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit); Plan de uitgevoerde Callable- taak om één keer te worden uitgevoerd na de vertraging die als argument is opgegeven.
public ScheduledFuture<?> scheduleAtFixedRate(Uitvoerbare opdracht, lange initialDelay, lange periode, TimeUnit-eenheid); Plan de periodieke uitvoering van de geslaagde taak, die voor het eerst wordt uitgevoerd na initialDelay , en elke volgende run begint na period .
public ScheduledFuture<?> scheduleWithFixedDelay(Uitvoerbare opdracht, lange initialDelay, lange vertraging, TimeUnit-eenheid); Plan de periodieke uitvoering van de voltooide taak, die voor de eerste keer wordt uitgevoerd na initialDelay , en elke volgende uitvoering begint na vertraging (de periode tussen de voltooiing van de vorige uitvoering en de start van de huidige).