Warum benötigen Sie die Executor-Schnittstelle?

Vor Java 5 mussten Sie die gesamte Code-Thread-Verwaltung in Ihrer Anwendung selbst schreiben. Darüber hinaus ist die Erstellung einerneues Themaobject ist ein ressourcenintensiver Vorgang und es macht keinen Sinn, für jede einfache Aufgabe einen neuen Thread zu erstellen. Und weil dieses Problem absolut jedem Entwickler von Multithread-Anwendungen bekannt ist, haben sie beschlossen, diese Funktionalität als Executor- Framework in Java zu integrieren.

Was ist die große Idee? Es ist ganz einfach: Anstatt für jede neue Aufgabe einen neuen Thread zu erstellen, werden Threads in einer Art „Speicher“ aufbewahrt, und wenn eine neue Aufgabe eintrifft, rufen wir einen vorhandenen Thread ab, anstatt einen neuen zu erstellen.

Die Hauptschnittstellen dieses Frameworks sind Executor , ExecutorService und ScheduledExecutorService , von denen jede die Funktionalität der vorherigen erweitert.

Die Executor-Schnittstelle ist die Basisschnittstelle. Es deklariert eine einzelne void-execute-Methode (ausführbarer Befehl) , die von einem ausführbaren Objekt implementiert wird .

Interessanter ist die ExecutorService- Schnittstelle. Es verfügt über Methoden zur Verwaltung des Arbeitsabschlusses sowie über Methoden zur Rückgabe bestimmter Ergebnisse. Schauen wir uns seine Methoden genauer an:

Methode Beschreibung
void Shutdown(); Der Aufruf dieser Methode stoppt den ExecutorService . Alle bereits zur Bearbeitung eingereichten Aufgaben werden erledigt, neue Aufgaben werden jedoch nicht angenommen.
List<Runnable> ShutdownNow();

Der Aufruf dieser Methode stoppt den ExecutorService . Thread.interrupt wird für alle Aufgaben aufgerufen, die bereits zur Verarbeitung übermittelt wurden. Diese Methode gibt eine Liste der in der Warteschlange befindlichen Aufgaben zurück.

Die Methode wartet nicht auf den Abschluss aller Aufgaben, die zum Zeitpunkt des Methodenaufrufs „in Bearbeitung“ sind.

Warnung: Durch den Aufruf dieser Methode können Ressourcen verloren gehen.

boolean isShutdown(); Überprüft, ob der ExecutorService gestoppt ist.
boolean isTerminated(); Gibt true zurück, wenn alle Aufgaben nach dem Herunterfahren des ExecutorService abgeschlossen wurden . Bis Shutdown() oder ShutdownNow() aufgerufen wird, wird immer false zurückgegeben .
boolean waitingTermination(long timeout, TimeUnit unit) throws InterruptedException;

Nachdem die Methode „shutdown()“ aufgerufen wurde, blockiert diese Methode den Thread, für den sie aufgerufen wird, bis eine der folgenden Bedingungen erfüllt ist:

  • alle geplanten Aufgaben sind abgeschlossen;
  • das an die Methode übergebene Timeout ist abgelaufen;
  • Der aktuelle Thread wird unterbrochen.

Gibt „true“ zurück , wenn alle Aufgaben abgeschlossen sind, und „ false“ , wenn das Timeout vor der Beendigung verstrichen ist.

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

Fügt dem ExecutorService eine aufrufbare Aufgabe hinzu und gibt ein Objekt zurück, das die Future- Schnittstelle implementiert .

<T> ist der Typ des Ergebnisses der übergebenen Aufgabe.

<T> Future<T> subscribe(Runnable task, T result);

Fügt dem ExecutorService eine ausführbare Aufgabe hinzu und gibt ein Objekt zurück, das die Future- Schnittstelle implementiert .

Der T-Ergebnisparameter ist das, was durch einen Aufruf der get()- Methode für das Ergebnis zurückgegeben wirdZukünftiges Objekt.

Future<?> subscribe(Runnable task);

Fügt dem ExecutorService eine ausführbare Aufgabe hinzu und gibt ein Objekt zurück, das die Future- Schnittstelle implementiert .

Wenn wir die Methode get() für das resultierende Future- Objekt aufrufen , erhalten wir null.

<T> List<Future<T>> invokeAll(Collection<? erweitert Callable<T>> task) throws InterruptedException;

Übergibt eine Liste aufrufbarer Aufgaben an den ExecutorService . Gibt eine Liste von Futures zurück, aus denen wir das Ergebnis der Arbeit erhalten können. Diese Liste wird zurückgegeben, wenn alle übermittelten Aufgaben abgeschlossen sind.

Wenn die Aufgabensammlung während der Ausführung der Methode geändert wird, ist das Ergebnis dieser Methode undefiniert.

<T> List<Future<T>> invokeAll(Collection<? erweitert Callable<T>>-Aufgaben, lange Zeitüberschreitung, TimeUnit-Einheit) löst InterruptedException aus;

Übergibt eine Liste aufrufbarer Aufgaben an den ExecutorService . Gibt eine Liste von Futures zurück, aus denen wir das Ergebnis der Arbeit erhalten können. Diese Liste wird zurückgegeben, wenn alle übergebenen Aufgaben abgeschlossen sind oder nachdem das an die Methode übergebene Timeout abgelaufen ist, je nachdem, was zuerst eintritt.

Wenn die Zeitüberschreitung abläuft, werden nicht erledigte Aufgaben abgebrochen.

Hinweis: Es ist möglich, dass die Ausführung einer abgebrochenen Aufgabe nicht gestoppt wird (diesen Nebeneffekt sehen wir im Beispiel).

Wenn die Aufgabensammlung während der Ausführung der Methode geändert wird, ist das Ergebnis dieser Methode undefiniert.

<T> T invokeAny(Collection<? erweitert Callable<T>> task) throws InterruptedException, ExecutionException;

Übergibt eine Liste aufrufbarer Aufgaben an den ExecutorService . Gibt das Ergebnis einer der Aufgaben (falls vorhanden) zurück, die ohne Auslösen einer Ausnahme (falls vorhanden) abgeschlossen wurden.

Wenn die Aufgabensammlung während der Ausführung der Methode geändert wird, ist das Ergebnis dieser Methode undefiniert.

<T> T invokeAny(Collection<? erweitert Callable<T>>-Aufgaben, langes Timeout, TimeUnit-Einheit) löst InterruptedException, ExecutionException, TimeoutException aus;

Übergibt eine Liste aufrufbarer Aufgaben an den ExecutorService . Gibt das Ergebnis einer der Aufgaben (falls vorhanden) zurück, die ohne Auslösen einer Ausnahme abgeschlossen wurde, bevor das an die Methode übergebene Timeout abgelaufen ist.

Wenn die Aufgabensammlung während der Ausführung der Methode geändert wird, ist das Ergebnis dieser Methode undefiniert.

Schauen wir uns ein kleines Beispiel für die Arbeit mit ExecutorService an .


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;
       }
   }
}

Ausgang:

done
done
Futures empfangen
Sleep 1: Schlaf unterbrochen
Sleep 1: Schlaf unterbrochen
done
done
true
true

Jede Aufgabe wird 5 Sekunden lang ausgeführt. Wir haben einen Pool für zwei Threads erstellt, daher sind die ersten beiden Ausgabezeilen absolut sinnvoll.

Sechs Sekunden nach dem Start des Programms läuft die Methode invokeAll ab und das Ergebnis wird als Liste von Futures zurückgegeben . Dies ist aus der Ausgabezeichenfolge „Futures empfangen“ ersichtlich .

Nachdem die ersten beiden Aufgaben erledigt sind, beginnen zwei weitere. Da jedoch das in der invokeAll- Methode festgelegte Zeitlimit abläuft, haben diese beiden Aufgaben keine Zeit zum Abschluss. Sie erhalten einen „Abbrechen“ -Befehl. Aus diesem Grund gibt es in der Ausgabe zwei Zeilen mit Sleep 1: Sleep Interrupted .

Und dann sehen Sie zwei weitere Zeilen mit done . Dies ist der Nebeneffekt, den ich bei der Beschreibung der invokeAll- Methode erwähnt habe.

Die fünfte und letzte Aufgabe wird nie gestartet, daher sehen wir in der Ausgabe nichts davon.

Die letzten beiden Zeilen sind das Ergebnis des Aufrufs der Methoden isShutdown und isTerminated .

Es ist auch interessant, dieses Beispiel im Debug-Modus auszuführen und sich den Aufgabenstatus nach Ablauf des Timeouts anzusehen (setzen Sie einen Haltepunkt in der Zeile mit executorService.shutdown(); ):

Wir sehen, dass zwei Aufgaben normal abgeschlossen wurden und drei Aufgaben „abgebrochen“ wurden .

ScheduledExecutorService

Um unsere Diskussion über Executoren abzuschließen, werfen wir einen Blick auf ScheduledExecutorService .

Es gibt 4 Methoden:

Methode Beschreibung
public ScheduledFuture<?> scheme(Ausführbarer Befehl, lange Verzögerung, TimeUnit-Einheit); Plant die übergebene ausführbare Aufgabe so, dass sie nach der als Argument angegebenen Verzögerung einmal ausgeführt wird.
öffentlicher <V> ScheduledFuture<V>-Zeitplan (Callable<V> aufrufbar, lange Verzögerung, TimeUnit-Einheit); Plant die übergebene aufrufbare Aufgabe so, dass sie nach der als Argument angegebenen Verzögerung einmal ausgeführt wird.
public ScheduledFuture<?> schemeAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit); Plant die regelmäßige Ausführung der übergebenen Aufgabe, die zum ersten Mal nach initialDelay ausgeführt wird , und jede weitere Ausführung beginnt nach period .
public ScheduledFuture<?> schemeWithFixedDelay(Ausführbarer Befehl, lange Anfangsverzögerung, lange Verzögerung, TimeUnit-Einheit); Plant die regelmäßige Ausführung der übergebenen Aufgabe, die zum ersten Mal nach initialDelay ausgeführt wird , und jede nachfolgende Ausführung beginnt nach einer Verzögerung (dem Zeitraum zwischen dem Abschluss der vorherigen Ausführung und dem Start der aktuellen Ausführung).