Bei der Entwicklung einer Multithread-Anwendung müssen wir uns normalerweise mit der Organisation der Thread-Arbeit befassen. Je größer unsere Anwendung und je mehr Threads wir für Multithread-Aufgaben benötigen, desto mehrLauffähigObjekte, die wir erstellen.

Hierbei ist zu beachten, dass das Erstellen eines Threads in Java ein ziemlich kostspieliger Vorgang ist. Wenn wir jedes Mal eine neue Instanz des Threads erstellen, um eine Operation auszuführen, werden wir große Probleme mit der Leistung und damit auch mit dem Zustand der Anwendung bekommen.

Hier helfen uns ein Thread-Pool und ThreadPoolExecutor .

Ein Thread-Pool ist eine Reihe vorinitialisierter Threads. Seine Größe kann fest oder variabel sein.

Wenn es mehr Aufgaben als Threads gibt, warten Aufgaben in einer Aufgabenwarteschlange. Der N-te Thread im Pool nimmt eine Aufgabe aus der Warteschlange, und nachdem sie erledigt ist, holt der Thread eine neue Aufgabe aus der Warteschlange. Sobald alle Aufgaben in der Warteschlange ausgeführt sind, bleiben die Threads aktiv und warten auf neue Aufgaben. Wenn neue Aufgaben auftauchen, beginnen die Threads ebenfalls mit deren Ausführung.

ThreadPoolExecutor

Ab Java 5 erhielt das Executor-Framework eine Multithreading-Lösung. Im Allgemeinen besteht es aus vielen Komponenten und soll uns dabei helfen, Warteschlangen und Thread-Pools effizient zu verwalten.

Die Hauptschnittstellen sind Executor und ExecutorService .

Executor ist eine Schnittstelle mit einer einzelnen void-execute-Methode (Runnable runnable).

Beachten Sie beim Übergeben einer Aufgabe an eine Implementierung dieser Methode, dass sie in Zukunft asynchron ausgeführt wird.

ExecutorService – Eine Schnittstelle, die die Executor- Schnittstelle erweitert und Funktionen zum Ausführen von Aufgaben hinzufügt. Es verfügt außerdem über Methoden zum Unterbrechen einer laufenden Aufgabe und zum Beenden des Thread-Pools.

ThreadPoolExecutor implementiert die Schnittstellen Executor und ExecutorService und trennt die Aufgabenerstellung von der Aufgabenausführung. Wir müssen ausführbare Objekte implementieren und sie an einen Executor senden. Der ThreadPoolExecutor ist dann dafür verantwortlich, die Aufgaben auszuführen und Threads zu erstellen und mit ihnen zu arbeiten.

Nachdem eine Aufgabe zur Ausführung gesendet wurde, wird ein vorhandener Thread im Pool verwendet. Dies verbessert die Leistung. Es löst das Problem der Verschwendung von Ressourcen für die Erstellung und Initialisierung eines neuen Threads und dann noch einmal für die Speicherbereinigung, sobald wir mit dem Thread fertig sind.

ThreadPoolExecutor hat 4 Konstruktoren:


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

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

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

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

Der ThreadPoolExecutor -Konstruktor verfügt über die folgenden Parameter:

corePoolSize Dieser Parameter gibt an, wie viele Threads bereit (gestartet) sind, wenn der Executor-Dienst startet.
maximalePoolSize Die maximale Anzahl von Threads, die ein Executor-Dienst erstellen kann.
keepAliveTime Die Zeit, die ein freigegebener Thread weiterlebt, bevor er zerstört wird, wenn die Anzahl der Threads größer als istcorePoolSize. Die Zeiteinheiten werden im nächsten Parameter angegeben.
Einheit Zeiteinheiten (Stunden, Minuten, Sekunden, Millisekunden usw.).
Arbeitswarteschlange Implementierung einer Warteschlange für Aufgaben.
Handler Handler für Aufgaben, die nicht abgeschlossen werden können.
ThreadFactory Ein Objekt, das bei Bedarf neue Threads erstellt. Durch die Verwendung von Thread-Factorys werden Aufrufe eines neuen Threads unabhängig von der Hardware, sodass Anwendungen spezielle Thread-Unterklassen, Prioritäten usw. verwenden können.

Erstellen eines ThreadPoolExecutors

Die Dienstprogrammklasse Executors kann die Erstellung eines ThreadPoolExecutor vereinfachen . Die Methoden dieser Utility-Klasse helfen uns bei der Vorbereitung einesThreadPoolExecutorObjekt.

newFixedThreadPool – Erstellt einen Thread-Pool, der eine feste Anzahl von Threads wiederverwendet, um eine beliebige Anzahl von Aufgaben auszuführen.

ExecutorService executor = Executors.newFixedThreadPool(10);
                    
newWorkStealingPool – Erstellt einen Thread-Pool, in dem die Anzahl der Threads der Anzahl der für die JVM verfügbaren Prozessorkerne entspricht. Die Standard-Parallelitätsstufe ist eins. Dies bedeutet, dass im Pool so viele Threads erstellt werden, wie der JVM CPU-Kerne zur Verfügung stehen. Wenn die Parallelitätsstufe 4 ist, wird der übergebene Wert anstelle der Anzahl der Kerne verwendet.

ExecutorService executor = Executors.newWorkStealingPool(4);
                    
newSingleThreadExecutor – Erstellt einen Pool mit einem einzelnen Thread zur Ausführung aller Aufgaben.

ExecutorService executor = Executors.newSingleThreadExecutor();
                    
newCachedThreadPool – Erstellt einen Thread-Pool, der bei Bedarf neue Threads erstellt, zuvor erstellte Threads jedoch wiederverwendet, wenn sie verfügbar sind.

ExecutorService executor = Executors.newCachedThreadPool();
                    
newScheduledThreadPool – Erstellt einen Thread-Pool, der die Ausführung von Befehlen nach einer bestimmten Verzögerung oder in regelmäßigen Abständen planen kann.

ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
                    

In den folgenden Lektionen werden wir jeden Pooltyp betrachten.