Suche…


Einführung

Die Executor- Schnittstelle in Java bietet die Möglichkeit, die Aufgabe von Aufgaben von den Mechanismen zu koppeln, wie die einzelnen Aufgaben ausgeführt werden, einschließlich Details zur Verwendung des Threads, zur Planung usw. Ein Executor wird normalerweise verwendet, anstatt Threads explizit zu erstellen. Mit Executors müssen Entwickler ihren Code nicht wesentlich umschreiben, um die Task-Ausführungsrichtlinie ihres Programms einfach anpassen zu können.

Bemerkungen

Fallstricke

  • Wenn Sie eine Task für die wiederholte Ausführung planen, wird Ihre Task je nach verwendetem ScheduledExecutorService möglicherweise vor jeder weiteren Ausführung angehalten, wenn die Ausführung Ihrer Task eine Ausnahme verursacht, die nicht behandelt wird. Siehe Mutter F ** k beim ScheduledExecutorService!

Feuer und Vergessen - ausführbare Aufgaben

Executoren akzeptieren eine java.lang.Runnable die (möglicherweise rechnerisch oder anderweitig andauernd oder stark) Code enthält, der in einem anderen Thread ausgeführt werden soll.

Verwendung wäre:

Executor exec = anExecutor;
exec.execute(new Runnable() {
    @Override public void run() {
        //offloaded work, no need to get result back
    }
});

Beachten Sie, dass Sie mit diesem Executor keine Möglichkeit haben, einen berechneten Wert zurückzubekommen.
Mit Java 8 kann man Lambdas verwenden, um das Codebeispiel zu verkürzen.

Java SE 8
Executor exec = anExecutor;
exec.execute(() -> {
    //offloaded work, no need to get result back
});

ThreadPoolExecutor

Ein häufig verwendeter Executor ist der ThreadPoolExecutor , der sich um die Thread-Behandlung kümmert. Sie können die minimale Anzahl an Threads konfigurieren, die der Executor immer beibehalten muss, wenn nicht viel zu tun ist (Core-Größe genannt) und eine maximale Thread-Größe, auf die der Pool wachsen kann, wenn mehr Arbeit zu erledigen ist. Wenn die Arbeitslast nachlässt, verringert der Pool die Thread-Anzahl langsam wieder, bis die Mindestgröße erreicht ist.

ThreadPoolExecutor pool = new ThreadPoolExecutor(
    1,                                     // keep at least one thread ready, 
                                           // even if no Runnables are executed
    5,                                     // at most five Runnables/Threads
                                           // executed in parallel
    1, TimeUnit.MINUTES,                   // idle Threads terminated after one
                                           // minute, when min Pool size exceeded
    new ArrayBlockingQueue<Runnable>(10)); // outstanding Runnables are kept here

pool.execute(new Runnable() {
    @Override public void run() {
        //code to run
    }
});

Hinweis Wenn Sie den ThreadPoolExecutor mit einer unbegrenzten Warteschlange konfigurieren, überschreitet die Thread-Anzahl nicht corePoolSize da neue Threads nur erstellt werden, wenn die Warteschlange voll ist:

ThreadPoolExecutor mit allen Parametern:

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)

von JavaDoc

Wenn mehr als corePoolSize, aber weniger als maximumPoolSize-Threads ausgeführt werden, wird ein neuer Thread nur erstellt, wenn die Warteschlange voll ist.

Vorteile:

  1. Die Größe der BlockingQueue kann gesteuert werden, und Szenarien mit zu wenig Arbeitsspeicher können vermieden werden. Die Anwendungsleistung wird durch die begrenzte beschränkte Warteschlangengröße nicht beeinträchtigt.

  2. Sie können vorhandene Richtlinien verwenden oder neue Richtlinien für Ablehnungshandler erstellen.

    1. In der Standardeinstellung ThreadPoolExecutor.AbortPolicy löst der Handler bei Ablehnung eine Laufzeit-RejectedExecutionException aus.

    2. In ThreadPoolExecutor.CallerRunsPolicy führt der Thread, der die Ausführung ausführt, die Aufgabe aus. Dies stellt einen einfachen Mechanismus zur Regelung der Rückkopplung bereit, der die Geschwindigkeit der Übergabe neuer Aufgaben verlangsamt.

    3. In ThreadPoolExecutor.DiscardPolicy wird eine Aufgabe, die nicht ausgeführt werden kann, einfach gelöscht.

    4. Wenn der Executor in ThreadPoolExecutor.DiscardOldestPolicy nicht heruntergefahren ist, wird die Task am Anfang der Arbeitswarteschlange gelöscht und die Ausführung wird erneut versucht (dies kann erneut fehlschlagen und führt dazu, dass dies wiederholt wird.)

  3. Benutzerdefinierte ThreadFactory kann konfiguriert werden, was nützlich ist:

    1. So legen Sie einen aussagekräftigeren Threadnamen fest
    2. So legen Sie den Thread-Daemon-Status fest
    3. Thread-Priorität einstellen

Hier ist ein Beispiel für die Verwendung von ThreadPoolExecutor

Wert aus der Berechnung abrufen - Aufrufbar

Wenn Ihre Berechnung einen Rückgabewert liefert, der später erforderlich ist, reicht eine einfache ausführbare Aufgabe nicht aus. In solchen Fällen können Sie ExecutorService.submit( Callable <T>) das nach Abschluss der Ausführung einen Wert zurückgibt.

Der Service gibt eine Future der Sie das Ergebnis der Taskausführung abrufen können.

// Submit a callable for execution
ExecutorService pool = anExecutorService;
Future<Integer> future = pool.submit(new Callable<Integer>() {
    @Override public Integer call() {
        //do some computation
        return new Random().nextInt();
    }
});    
// ... perform other tasks while future is executed in a different thread

Wenn Sie das Ergebnis der Zukunft benötigen, rufen Sie future.get()

  • Warten Sie unbegrenzt auf die Zukunft, um mit einem Ergebnis fertig zu werden.

      try {
          // Blocks current thread until future is completed
          Integer result = future.get(); 
      catch (InterruptedException || ExecutionException e) {
          // handle appropriately
      }
    
  • Warten Sie, bis die Zukunft abgeschlossen ist, jedoch nicht länger als die angegebene Zeit.

      try {
          // Blocks current thread for a maximum of 500 milliseconds.
          // If the future finishes before that, result is returned,
          // otherwise TimeoutException is thrown.
          Integer result = future.get(500, TimeUnit.MILLISECONDS); 
      catch (InterruptedException || ExecutionException || TimeoutException e) {
          // handle appropriately
      }
    

Wenn das Ergebnis einer geplanten oder laufenden Aufgabe nicht mehr erforderlich ist, können Sie Future.cancel(boolean) aufrufen, um den Future.cancel(boolean) abzubrechen.

  • Durch Aufrufen von cancel(false) wird die Aufgabe nur aus der Warteschlange der auszuführenden Aufgaben entfernt.
  • Wenn Sie cancel(true) aufrufen cancel(true) wird die Task auch unterbrochen, wenn sie gerade ausgeführt wird.

Planen von Aufgaben zur Ausführung zu einer festgelegten Zeit, nach einer Verzögerung oder wiederholt

Die ScheduledExecutorService Klasse stellt Methoden zum Planen einzelner oder wiederholter Aufgaben auf verschiedene Arten bereit. Im folgenden Codebeispiel wird davon ausgegangen, dass der pool wie folgt deklariert und initialisiert wurde:

ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);

Zusätzlich zu den normalen ExecutorService Methoden fügt die ScheduledExecutorService API vier Methoden hinzu, die Aufgaben planen und ScheduledFuture Objekte zurückgeben. Letzteres kann zum Abrufen von Ergebnissen (in einigen Fällen) und zum Abbrechen von Aufgaben verwendet werden.

Start einer Aufgabe nach einer festen Verzögerung

Im folgenden Beispiel wird der Start einer Aufgabe nach zehn Minuten geplant.

ScheduledFuture<Integer> future = pool.schedule(new Callable<>() {
        @Override public Integer call() {
            // do something
            return 42;
        }
    }, 
    10, TimeUnit.MINUTES);

Aufgaben zu einem festen Preis beginnen

Im folgenden Beispiel wird eine Task so geplant, dass sie nach zehn Minuten startet und dann alle 1 Minute wiederholt wird.

ScheduledFuture<?> future = pool.scheduleAtFixedRate(new Runnable() {
        @Override public void run() {
            // do something
        }
    }, 
    10, 1, TimeUnit.MINUTES);

Die Task-Ausführung wird gemäß dem Zeitplan fortgesetzt, bis der pool heruntergefahren wird, die future abgebrochen wird oder eine der Aufgaben auf eine Ausnahme stößt.

Es ist garantiert, dass sich die von einem bestimmten scheduledAtFixedRate Aufruf scheduledAtFixedRate Tasks nicht zeitlich überlappen. Wenn eine Task länger als die vorgeschriebene Zeit dauert, können die nächste und die nachfolgenden Taskausführungen zu spät beginnen.

Aufgaben werden mit einer festen Verzögerung gestartet

Im folgenden Beispiel wird eine Task so geplant, dass sie nach zehn Minuten startet und dann wiederholt mit einer Verzögerung von einer Minute zwischen einer Taskende und der nächsten Task startet.

ScheduledFuture<?> future = pool.scheduleWithFixedDelay(new Runnable() {
        @Override public void run() {
            // do something
        }
    }, 
    10, 1, TimeUnit.MINUTES);

Die Task-Ausführung wird gemäß dem Zeitplan fortgesetzt, bis der pool heruntergefahren wird, die future abgebrochen wird oder eine der Aufgaben auf eine Ausnahme stößt.

Abgelehnte Ausführung von Handle

Ob

  1. Sie versuchen, Aufgaben an einen heruntergefahrenen Executor zu übergeben oder
  2. Die Warteschlange ist gesättigt (nur bei beschränkten möglich) und die maximale Anzahl Threads wurde erreicht.

RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor) wird aufgerufen.

Das Standardverhalten ist, dass beim Aufrufer eine RejectedExecutionException ausgelöst wird. Es sind jedoch weitere vordefinierte Verhaltensweisen verfügbar:

  • ThreadPoolExecutor.AbortPolicy (standardmäßig REE werfen)
  • ThreadPoolExecutor.CallerRunsPolicy (führt eine Task im Thread des Aufrufers aus - blockiert ihn )
  • ThreadPoolExecutor.DiscardPolicy (Task im Hintergrund verwerfen)
  • ThreadPoolExecutor.DiscardOldestPolicy ( älteste Aufgabe in der Warteschlange automatisch verwerfen und Ausführung der neuen Aufgabe erneut versuchen )

Sie können sie mit einem der ThreadPool- Konstruktoren festlegen:

public ThreadPoolExecutor(int corePoolSize,
                      int maximumPoolSize,
                      long keepAliveTime,
                      TimeUnit unit,
                      BlockingQueue<Runnable> workQueue,
                      RejectedExecutionHandler handler) // <--

public ThreadPoolExecutor(int corePoolSize,
                      int maximumPoolSize,
                      long keepAliveTime,
                      TimeUnit unit,
                      BlockingQueue<Runnable> workQueue,
                      ThreadFactory threadFactory,
                      RejectedExecutionHandler handler) // <--

Sie können auch Ihr eigenes Verhalten implementieren, indem Sie die RejectedExecutionHandler- Schnittstelle erweitern:

void rejectedExecution(Runnable r, ThreadPoolExecutor executor)

Unterschiede in der Übergabe von Ausnahmen () im Vergleich zur Ausführung ()

Der Befehl execute () wird im Allgemeinen für Feuer- und Vergissaufrufe verwendet (ohne dass das Ergebnis analysiert werden muss), und der Befehl submit () wird zum Analysieren des Ergebnisses des Future-Objekts verwendet.

Wir sollten uns der Hauptunterschiede der Ausnahmebehandlungsmechanismen zwischen diesen beiden Befehlen bewusst sein.

Ausnahmen von submit () werden vom Framework verschluckt, wenn Sie sie nicht abgefangen haben.

Codebeispiel, um den Unterschied zu verstehen:

Fall 1: Übergeben Sie den Befehl Runnable with execute (), der die Ausnahme meldet.

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

public class ExecuteSubmitDemo {
    public ExecuteSubmitDemo() {
        System.out.println("creating service");
        ExecutorService service = Executors.newFixedThreadPool(2);
        //ExtendedExecutor service = new ExtendedExecutor();
        for (int i = 0; i < 2; i++){
            service.execute(new Runnable(){
                 public void run(){
                    int a = 4, b = 0;
                    System.out.println("a and b=" + a + ":" + b);
                    System.out.println("a/b:" + (a / b));
                    System.out.println("Thread Name in Runnable after divide by zero:"+Thread.currentThread().getName());
                 }
            });
        }
        service.shutdown();
    }
    public static void main(String args[]){
        ExecuteSubmitDemo demo = new ExecuteSubmitDemo();
    }
}

class ExtendedExecutor extends ThreadPoolExecutor {

   public ExtendedExecutor() { 
       super(1, 1, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));
   }
   // ...
   protected void afterExecute(Runnable r, Throwable t) {
     super.afterExecute(r, t);
     if (t == null && r instanceof Future<?>) {
       try {
         Object result = ((Future<?>) r).get();
       } catch (CancellationException ce) {
           t = ce;
       } catch (ExecutionException ee) {
           t = ee.getCause();
       } catch (InterruptedException ie) {
           Thread.currentThread().interrupt(); // ignore/reset
       }
     }
     if (t != null)
       System.out.println(t);
   }
 }

Ausgabe:

creating service
a and b=4:0
a and b=4:0
Exception in thread "pool-1-thread-1" Exception in thread "pool-1-thread-2" java.lang.ArithmeticException: / by zero
        at ExecuteSubmitDemo$1.run(ExecuteSubmitDemo.java:15)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:744)
java.lang.ArithmeticException: / by zero
        at ExecuteSubmitDemo$1.run(ExecuteSubmitDemo.java:15)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:744)

Fall 2: Ersetzen von service.submit(new Runnable(){ (): service.submit(new Runnable(){ In diesem Fall werden Ausnahmen vom Framework verschluckt, da die run () - Methode sie nicht explizit service.submit(new Runnable(){ .

Ausgabe:

creating service
a and b=4:0
a and b=4:0

Fall 3: Ändern Sie den neuenFixedThreadPool in ExtendedExecutor

//ExecutorService service = Executors.newFixedThreadPool(2);
 ExtendedExecutor service = new ExtendedExecutor(); 

Ausgabe:

creating service
a and b=4:0
java.lang.ArithmeticException: / by zero
a and b=4:0
java.lang.ArithmeticException: / by zero

Ich habe dieses Beispiel für zwei Themen gezeigt: Verwenden Sie Ihren benutzerdefinierten ThreadPoolExecutor und behandeln Sie Exectpion mit einem benutzerdefinierten ThreadPoolExecutor.

Eine andere einfache Lösung für das obige Problem: Wenn Sie den normalen ExecutorService- & Submit-Befehl verwenden, rufen Sie das Future-Objekt über den submit () - Befehlsaufruf get () für Future ab. Erfassen Sie die drei Ausnahmen, die in der Implementierung der afterExecute-Methode angegeben wurden. Vorteil des benutzerdefinierten ThreadPoolExecutor gegenüber diesem Ansatz: Sie müssen den Ausnahmebehandlungsmechanismus nur an einer Stelle behandeln - Custom ThreadPoolExecutor.

Anwendungsfälle für verschiedene Arten von Parallelitätskonstrukten

  1. ExecutorService

    ExecutorService executor = Executors.newFixedThreadPool(50);

    Es ist einfach und leicht zu bedienen. Es verdeckt ThreadPoolExecutor Details von ThreadPoolExecutor .

    Ich ziehe dieses vor, wenn die Anzahl der Callable/Runnable Aufgaben gering ist und die Anzahl der Aufgaben in der unbegrenzten Warteschlange den Speicher nicht erhöht und die Leistung des Systems nicht beeinträchtigt. Wenn Sie über CPU/Memory verfügen, ziehe ich es vor, ThreadPoolExecutor mit Kapazitätsbeschränkungen und RejectedExecutionHandler zu verwenden, um die Ablehnung von Aufgaben auszuführen.

  2. CountDownLatch

    CountDownLatch wird mit einer bestimmten Anzahl initialisiert. Diese Anzahl wird durch Aufrufe der countDown() -Methode countDown() . Threads, die darauf warten, dass diese Anzahl Null erreicht, können eine der await() Methoden aufrufen. Der Aufruf von await() blockiert den Thread, bis der Zähler null erreicht. Diese Klasse ermöglicht es einem Java-Thread, zu warten, bis eine andere Gruppe von Threads ihre Aufgaben abgeschlossen hat.

    Anwendungsfälle:

    1. Maximale Parallelität erreichen: Manchmal möchten wir mehrere Threads gleichzeitig starten, um maximale Parallelität zu erreichen

    2. Warten Sie, bis N-Threads abgeschlossen sind, bevor Sie die Ausführung starten

    3. Deadlock-Erkennung

  1. ThreadPoolExecutor : Es bietet mehr Kontrolle. Wenn die Anwendung durch die Anzahl der ausstehenden ausgeführten / aufrufbaren Aufgaben eingeschränkt wird, können Sie die beschränkte Warteschlange verwenden, indem Sie die maximale Kapazität festlegen. Sobald die Warteschlange die maximale Kapazität erreicht hat, können Sie RejectionHandler definieren. Java bietet vier Arten von RejectedExecutionHandler Richtlinien .

    1. ThreadPoolExecutor.AbortPolicy , der Handler ThreadPoolExecutor.AbortPolicy bei Ablehnung eine Laufzeit-RejectedExecutionException aus.

    2. ThreadPoolExecutor.CallerRunsPolicy`, der Thread, der die Ausführung ausführt, führt die Aufgabe aus. Dies stellt einen einfachen Mechanismus zur Regelung der Rückkopplung bereit, der die Geschwindigkeit der Übergabe neuer Aufgaben verlangsamt.

    3. In ThreadPoolExecutor.DiscardPolicy wird eine Aufgabe, die nicht ausgeführt werden kann, einfach gelöscht.

    4. ThreadPoolExecutor.DiscardOldestPolicy : Wenn der Executor nicht heruntergefahren wird, wird die Task am Anfang der Arbeitswarteschlange gelöscht und die Ausführung wird erneut versucht (dies kann erneut fehlschlagen und führt dazu, dass dies wiederholt wird.)

Wenn Sie das CountDownLatch Verhalten simulieren möchten, können Sie die invokeAll() Methode verwenden.

  1. Ein weiterer Mechanismus, den Sie nicht zitiert haben, ist ForkJoinPool

    Der ForkJoinPool wurde Java in Java 7 hinzugefügt. Der ForkJoinPool ähnelt dem Java ExecutorService jedoch mit einem Unterschied. Mit dem ForkJoinPool können Aufgaben einfach in kleinere Aufgaben aufgeteilt werden, die dann ebenfalls an den ForkJoinPool werden. Das Stehlen von Aufgaben geschieht in ForkJoinPool wenn freie Worker-Threads Aufgaben aus der beschäftigten Worker-Warteschlange stehlen.

    Java 8 hat in ExecutorService eine weitere API zum Erstellen von Workstealing-Pools eingeführt. Sie müssen RecursiveTask und RecursiveAction nicht erstellen, können jedoch weiterhin ForkJoinPool .

    public static ExecutorService newWorkStealingPool()
    

    Erstellt einen Worksteal-Thread-Pool, der alle verfügbaren Prozessoren als Ziel-Parallelitätsebene verwendet.

    Standardmäßig wird die Anzahl der CPU-Kerne als Parameter verwendet.

Alle diese vier Mechanismen ergänzen einander. Abhängig von der Granularität, die Sie steuern möchten, müssen Sie die richtige auswählen.

Warten Sie auf den Abschluss aller Aufgaben in ExecutorService

Schauen wir uns die verschiedenen Optionen an, um zu warten, bis die an Executor gesendeten Aufgaben abgeschlossen sind

  1. ExecutorService invokeAll()

    Führt die angegebenen Aufgaben aus und gibt eine Liste der Futures zurück, die ihren Status und die Ergebnisse enthalten, wenn alles abgeschlossen ist.

Beispiel:

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

public class InvokeAllDemo{
    public InvokeAllDemo(){
        System.out.println("creating service");
        ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        
        List<MyCallable> futureList = new ArrayList<MyCallable>();
        for (int i = 0; i < 10; i++){
            MyCallable myCallable = new MyCallable((long)i);
            futureList.add(myCallable);
        }
        System.out.println("Start");
        try{
            List<Future<Long>> futures = service.invokeAll(futureList);  
        } catch(Exception err){
            err.printStackTrace();
        }
        System.out.println("Completed");
        service.shutdown();
    }
    public static void main(String args[]){
        InvokeAllDemo demo = new InvokeAllDemo();
    }
    class MyCallable implements Callable<Long>{
        Long id = 0L;
        public MyCallable(Long val){
            this.id = val;
        }
        public Long call(){
            // Add your business logic
            return id;
        }
    }
}
  1. CountDownLatch

    Eine Synchronisationshilfe, mit der ein oder mehrere Threads warten können, bis ein Satz von Vorgängen in anderen Threads abgeschlossen ist.

    Ein CountDownLatch wird mit einer bestimmten Anzahl initialisiert. Die wait-Methoden werden blockiert, bis der aktuelle Zählerstand null ist, weil countDown() der countDown() -Methode countDown() , wonach alle wartenden Threads freigegeben werden und alle nachfolgenden Aufrufe von await sofort zurückgegeben werden. Dies ist ein einmaliges Phänomen - die Zählung kann nicht zurückgesetzt werden. Wenn Sie eine Version benötigen, die die Zählung zurücksetzt, sollten Sie einen CyclicBarrier verwenden .

  2. ForkJoinPool oder newWorkStealingPool() in Executors

  3. Durchlaufen Sie alle Future Objekte, die nach dem Senden an ExecutorService

  4. Empfohlene Methode zum Herunterfahren von der Oracle-Dokumentationsseite von ExecutorService :

    void shutdownAndAwaitTermination(ExecutorService pool) {
        pool.shutdown(); // Disable new tasks from being submitted
        try {
          // Wait a while for existing tasks to terminate
          if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
            pool.shutdownNow(); // Cancel currently executing tasks
            // Wait a while for tasks to respond to being cancelled
            if (!pool.awaitTermination(60, TimeUnit.SECONDS))
                System.err.println("Pool did not terminate");
          }
        } catch (InterruptedException ie) {
          // (Re-)Cancel if current thread also interrupted
          pool.shutdownNow();
          // Preserve interrupt status
          Thread.currentThread().interrupt();
        }
    

    shutdown(): Startet ein ordnungsgemäßes Herunterfahren, bei dem zuvor gesendete Aufgaben ausgeführt werden, jedoch keine neuen Aufgaben angenommen werden.

    shutdownNow(): Versucht, alle aktiv ausgeführten Aufgaben zu stoppen, hält die Verarbeitung wartender Aufgaben an und gibt eine Liste der Aufgaben zurück, die auf ihre Ausführung warteten.

    Wenn im obigen Beispiel Ihre Aufgaben mehr Zeit in Anspruch nehmen, können Sie den if-Zustand in den while-Zustand ändern

    Ersetzen

    if (!pool.awaitTermination(60, TimeUnit.SECONDS))
    

    mit

    while(!pool.awaitTermination(60, TimeUnit.SECONDS)) {
      Thread.sleep(60000);
    

    }

Anwendungsfälle für verschiedene Typen von ExecutorService

Executors gibt verschiedene Arten von ThreadPools zurück, die auf die jeweiligen Bedürfnisse zugeschnitten sind.

  1. public static ExecutorService newSingleThreadExecutor()

    Erstellt einen Executor, der einen einzelnen Worker-Thread verwendet, der in einer unbegrenzten Warteschlange ausgeführt wird

    Es gibt einen Unterschied zwischen newFixedThreadPool(1) und newSingleThreadExecutor() wie das Java-Dokument für Letzteres sagt:

    Im Gegensatz zum ansonsten äquivalenten newFixedThreadPool (1) ist der zurückgegebene Executor garantiert nicht für die Verwendung zusätzlicher Threads rekonfigurierbar.

    newFixedThreadPool bedeutet, dass ein newFixedThreadPool später im Programm neu newFixedThreadPool kann durch: ((ThreadPoolExecutor) fixedThreadPool).setMaximumPoolSize(10) Dies ist für newSingleThreadExecutor nicht möglich

    Anwendungsfälle:

    1. Sie möchten die übergebenen Aufgaben in einer Reihenfolge ausführen.
    2. Sie benötigen nur einen Thread, um alle Ihre Anfragen zu bearbeiten

    Nachteile:

    1. Ungebundene Warteschlangen sind schädlich
  2. public static ExecutorService newFixedThreadPool(int nThreads)

    Erstellt einen Thread-Pool, der eine feste Anzahl von Threads wiederverwendet, die in einer gemeinsam genutzten, nicht gebundenen Warteschlange ausgeführt werden. Zu jedem Zeitpunkt sind höchstens nThreads-Threads aktive Verarbeitungsaufgaben. Wenn zusätzliche Tasks übergeben werden, während alle Threads aktiv sind, warten sie in der Warteschlange, bis ein Thread verfügbar ist

    Anwendungsfälle:

    1. Effektive Nutzung der verfügbaren Kerne. Konfigurieren Sie nThreads als Runtime.getRuntime().availableProcessors()
    2. Wenn Sie entscheiden, dass die Anzahl der Threads eine Anzahl im Thread-Pool nicht überschreiten soll

    Nachteile:

    1. Ungebundene Warteschlangen sind schädlich.
  3. public static ExecutorService newCachedThreadPool()

    Erstellt einen Thread-Pool, der nach Bedarf neue Threads erstellt, zuvor erstellte Threads jedoch wieder verwendet, wenn sie verfügbar sind

    Anwendungsfälle:

    1. Für kurzlebige asynchrone Aufgaben

    Nachteile:

    1. Ungebundene Warteschlangen sind schädlich.
    2. Jede neue Aufgabe erstellt einen neuen Thread, wenn alle vorhandenen Threads belegt sind. Wenn die Task längere Zeit in Anspruch nimmt, werden mehr Threads erstellt, was die Leistung des Systems beeinträchtigt. Alternative in diesem Fall: newFixedThreadPool
  1. public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

    Erstellt einen Thread-Pool, der Befehle planen kann, die nach einer bestimmten Verzögerung ausgeführt werden oder regelmäßig ausgeführt werden.

    Anwendungsfälle:

    1. Behandlung wiederkehrender Ereignisse mit Verzögerungen, die in bestimmten Zeitabständen in der Zukunft auftreten werden

    Nachteile:

    1. Ungebundene Warteschlangen sind schädlich.

    5. public static ExecutorService newWorkStealingPool()

    Erstellt einen Worksteal-Thread-Pool, der alle verfügbaren Prozessoren als Ziel-Parallelitätsebene verwendet

    Anwendungsfälle:

    1. Für das Teilen und Erobern von Problemen.
    2. Effektive Verwendung von im Leerlauf befindlichen Threads. Leere Threads stehlen Aufgaben aus ausgelasteten Threads.

    Nachteile:

    1. Nicht gebundene Warteschlangengröße ist schädlich.

In all diesen ExecutorService können Sie einen Nachteil feststellen: die unbegrenzte Warteschlange. Dies wird mit ThreadPoolExecutor angesprochen

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)

Mit ThreadPoolExecutor können Sie

  1. Kontrollieren Sie die Thread-Poolgröße dynamisch
  2. Stellen Sie die Kapazität für BlockingQueue
  3. Definieren Sie RejectionExecutionHander wenn die Warteschlange voll ist
  4. CustomThreadFactory , um einige zusätzliche Funktionen während der Thread-Erstellung hinzuzufügen (public Thread newThread(Runnable r)

Thread-Pools verwenden

Thread-Pools werden hauptsächlich in ExecutorService .

Die folgenden Methoden können verwendet werden, um Arbeit zur Ausführung einzureichen:

Methode Beschreibung
submit Führt die eingereichte Arbeit aus und gibt eine Zukunft zurück, die zum Abrufen des Ergebnisses verwendet werden kann
execute Führen Sie die Aufgabe irgendwann in der Zukunft aus, ohne einen Rückgabewert zu erhalten
invokeAll Führen Sie eine Liste mit Aufgaben aus und geben Sie eine Liste mit Futures zurück
invokeAny Führt alle aus, gibt jedoch nur das Ergebnis eines erfolgreichen Ergebnisses zurück (ohne Ausnahmen)

Wenn Sie mit dem Thread-Pool fertig sind, können Sie shutdown() aufrufen, um den Thread-Pool zu beenden. Dadurch werden alle anstehenden Aufgaben ausgeführt. Um auf die Ausführung aller Tasks zu warten, können Sie awaitTermination oder isShutdown() .



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow