concurrency java semaphore
In diesem Tutorial werden Komponenten des Pakets java.util.concurrent wie Java Semaphore, Executor Framework und ExecutorService zur Implementierung von Concurrency in Java erläutert:
Aus unseren vorherigen Java-Tutorials wissen wir, dass die Java-Plattform die gleichzeitige Programmierung von Grund auf unterstützt. Die Grundeinheit der Parallelität ist ein Thread, und wir haben Threads und Multithreading in Java ausführlich besprochen.
Ab Java 5 wurde der Java-Plattform ein Paket mit dem Namen 'java.util.concurrent' hinzugefügt. Dieses Paket enthält eine Reihe von Klassen und Bibliotheken, die es dem Programmierer erleichtern, gleichzeitige (Multithread-) Anwendungen zu entwickeln. Mit diesem Paket müssen wir keine komplexen Klassen schreiben, da wir die meisten gleichzeitigen Konzepte bereits implementiert haben.
=> Überprüfen Sie ALLE Java-Tutorials hier.
In diesem Tutorial werden die verschiedenen Komponenten des Pakets java.util.concurrent in Bezug auf Parallelität und Multithreading in Java erläutert.
Was du lernen wirst:
java.util.concurrent-Paket
Nachfolgend sind die verschiedenen Komponenten des Pakets java.util.concurrent aufgeführt, die sich auf Parallelität und Multithreading in Java beziehen. Lassen Sie uns jede Komponente anhand einfacher Programmierbeispiele im Detail untersuchen. Einige der Komponenten werden wir
diskutieren sind:
- Executor Framework
- ExecutorService
- ThreadPool
- Abrufbar
- Locks- ReentrantLock
- Semaphor
- ForkJoinPool
Executor Framework In Java
Das Executor Framework in Java wurde mit der JDK 5-Version veröffentlicht. Das Executor Framework (java.util.concurrent.Executor) ist ein Framework, das aus Komponenten besteht, mit denen wir mehrere Threads effizient verarbeiten können.
Mit dem Executor Framework können wir ausführbare Objekte ausführen, indem wir die bereits vorhandenen Threads wiederverwenden. Wir müssen nicht jedes Mal neue Threads erstellen, wenn wir Objekte ausführen müssen.
Die Executor-API trennt oder entkoppelt die Ausführung einer Aufgabe von der eigentlichen Aufgabe mithilfe von a Testamentsvollstrecker . Ein Executor ist auf der Executor-Schnittstelle zentriert und hat Unterschnittstellen, d.h. ExecutorService und die Klasse ThreadPoolExecutor.
Mit Executor müssen wir also nur ausführbare Objekte erstellen und diese dann an den Executor senden, der sie ausführt.
Einige der Best Practices, die bei der Verwendung des Executor-Frameworks zu beachten sind, sind:
- Wir sollten einen Code überprüfen und planen, um die Top-Listen zu überprüfen, damit wir sowohl Deadlocks als auch Livelocks im Code erkennen können.
- Java-Code sollte immer gegen statische Analysetools ausgeführt werden. Beispiele der statischen Analysewerkzeuge sind FindBugs und PMD.
- Wir sollten nicht nur Ausnahmen abfangen, sondern auch die Fehler in Multithread-Programmen.
Lassen Sie uns nun die Komponenten von Executor Framework in Java diskutieren.
Testamentsvollstrecker
Der Executor kann als Schnittstelle definiert werden, die zur Darstellung eines Objekts verwendet wird, das die ihm bereitgestellten Aufgaben ausführt. Ob die Aufgabe auf einem aktuellen oder einem neuen Thread ausgeführt werden soll, hängt von dem Punkt ab, von dem aus der Aufruf initiiert wurde, was weiter von der Implementierung abhängt.
Mit Executor können wir also die Aufgaben von der eigentlichen Aufgabe entkoppeln und sie dann asynchron ausführen.
Die Ausführung der Aufgabe mit Executor muss jedoch nicht asynchron sein. Ausführende können die Aufgabe auch sofort über den aufrufenden Thread aufrufen.
Im Folgenden finden Sie ein Beispiel für einen Code zum Erstellen einer Executor-Instanz:
public class Invoker implements Executor { @Override public void execute (Runnable r_interface) { r_interface.run(); } }
Sobald der Aufrufer wie oben gezeigt erstellt wurde, können wir ihn verwenden, um die Aufgabe wie folgt auszuführen.
public void execute () { Executor executor = new Invoker (); executor.execute ( () -> { //perform task }); }
Beachten Sie, dass wenn die Aufgabe vom Executor nicht akzeptiert wird, eine RejectedExecutionException ausgelöst wird.
ExecutorService
Ein ExecutorService (java.util.concurrent.ExecutorService) plant die übermittelten Aufgaben gemäß der Verfügbarkeit von Threads und verwaltet auch eine Speicherwarteschlange. Der ExecutorService fungiert als Komplettlösung für die asynchrone Verarbeitung von Aufgaben.
Um ExecutorService im Code zu verwenden, erstellen wir eine Runnable-Klasse. Der ExecutorService verwaltet einen Thread-Pool und weist die Aufgaben auch den Threads zu. Aufgaben können auch in die Warteschlange gestellt werden, falls der Thread nicht verfügbar ist.
Im Folgenden finden Sie ein einfaches Beispiel für ExecutorService.
import java.util.concurrent.*; public class Main { public static void main(String() args) { //create ExecutorService instance with 10 threads ExecutorService executor_Service = Executors.newFixedThreadPool(10); //assign the service to Runnable instance executor_Service.execute(new Runnable() { @Override public void run() { //print the message System.out.println('Simple Example of ExecutorService!!!'); } }); //shutdown executorService executor_Service.shutdown(); } }
Ausgabe
Im obigen Programm erstellen wir eine einfache ExecutorService-Instanz mit einem Thread-Pool, der aus 10 Threads besteht. Es wird dann der ausführbaren Instanz zugewiesen und ausgeführt, um die obige Nachricht zu drucken. Nach dem Drucken der Nachricht wird der ExecutorService heruntergefahren.
Thread-Pool
Ein Thread-Pool in Java ist eine Gruppe von Arbeitsthreads, die mehrfach wiederverwendet und Jobs zugewiesen werden können.
Ein Thread-Pool enthält eine Gruppe von Threads mit fester Größe. Jeder Thread wird aus dem Thread-Pool herausgezogen und vom Dienstanbieter mit einer Aufgabe versehen. Sobald der zugewiesene Job abgeschlossen ist, wird der Thread erneut an den Thread-Pool übergeben.
Der Thread-Pool ist vorteilhaft, da wir nicht jedes Mal, wenn die Aufgabe verfügbar ist, einen neuen Thread erstellen müssen, wodurch die Leistung verbessert wird. Es wird in Echtzeitanwendungen verwendet, die Servlet und JSP verwenden, wobei Thread-Pools zum Verarbeiten von Anforderungen verwendet werden.
In Multithread-Anwendungen spart der Thread-Pool Ressourcen und hilft, die Parallelität innerhalb vordefinierter Grenzen zu halten.
Das folgende Java-Programm demonstriert den Thread-Pool in Java.
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class WorkerThreadClass implements Runnable { private String message; //thread class constructor public WorkerThreadClass(String s){ this.message=s; } //run method for thread public void run() { System.out.println(' Start: '+message); processmessage(); //sleep between start and end System.out.println(' End: '+ message); } //processmessage method => sleeps the thread for 2 sec private void processmessage() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } public class Main { public static void main(String() args) { //create a ExecutorService instance ExecutorService executor = Executors.newFixedThreadPool(5);//creating a pool of 5 threads //create thread instances and execute them for (int i = 0; i <5; i++) { Runnable workerThrd = new WorkerThreadClass('Thread_' + i); executor.execute(workerThrd);//calling execute method of ExecutorService } //shutdown ExecutorService executor.shutdown(); while (!executor.isTerminated()) { } System.out.println('Finished all threads'); } }
Ausgabe
In den oben genannten Programmen gibt es einen Thread-Pool von 5 Threads, die mit der Methode 'newFixedThreadPool' erstellt werden. Anschließend werden die Threads erstellt und dem Pool hinzugefügt und dem ExecutorService zur Ausführung zugewiesen.
In Java aufrufbar
Wir wissen bereits, dass wir Threads mit zwei Ansätzen erstellen können. Ein Ansatz besteht darin, die Thread-Klasse zu erweitern, während der zweite Ansatz darin besteht, eine ausführbare Schnittstelle zu implementieren.
Threads, die mit der Runnable-Schnittstelle erstellt wurden, haben jedoch keine Funktion, d. H. Sie geben kein Ergebnis zurück, wenn der Thread beendet wird oder run () die Ausführung abschließt. Hier kommt die Callable-Schnittstelle ins Spiel.
Mit einer Callable-Schnittstelle definieren wir eine Aufgabe so, dass sie ein Ergebnis zurückgibt. Es kann auch eine Ausnahme auslösen. Die Callable-Schnittstelle ist Teil des Pakets java.util.concurrent.
Die Callable-Schnittstelle stellt eine call () -Methode bereit, die sich in ähnlichen Zeilen befindet wie die von der Runnable-Schnittstelle bereitgestellte run () -Methode, mit dem einzigen Unterschied, dass die call () -Methode einen Wert zurückgibt und eine geprüfte Ausnahme auslöst.
Die call () -Methode der Callable-Schnittstelle hat den folgenden Prototyp.
public Object call () throws Exception;
Da die call () -Methode ein Objekt zurückgibt, muss dies dem Hauptthread bekannt sein.
Daher sollte der Rückgabewert in einem anderen Objekt gespeichert werden, das dem Hauptthread bekannt ist. Diesem Zweck wird durch die Verwendung eines 'Future' -Objekts gedient. Ein Future-Objekt ist ein Objekt, das das von einem Thread zurückgegebene Ergebnis enthält. Mit anderen Worten, es wird das Ergebnis enthalten, wenn Callable zurückkehrt.
Callable kapselt eine Aufgabe, die auf einem anderen Thread ausgeführt werden soll. Ein Future-Objekt speichert das von einem anderen Thread zurückgegebene Ergebnis.
Eine aufrufbare Schnittstelle kann nicht zum Erstellen eines Threads verwendet werden. Wir brauchen Runnable, um einen Thread zu erstellen. Um das Ergebnis zu speichern, ist dann ein Future-Objekt erforderlich. Java bietet einen konkreten Typ namens 'FutureTask', der die Funktionalität kombiniert, indem er sowohl Runnable als auch Future implementiert.
Wir erstellen eine FutureTask, indem wir einem Konstruktor Callable bereitstellen. Dieses FutureTask-Objekt wird dann an den Konstruktor der Thread-Klasse übergeben, um ein Thread-Objekt zu erstellen.
Im Folgenden finden Sie ein Java-Programm, das die Callable-Schnittstelle und das Future-Objekt demonstriert. Wir verwenden in diesem Programm auch das FutureTask-Objekt.
Wie bereits erwähnt, erstellen wir im Programm eine Klasse, die eine Callable-Schnittstelle mit einer überschriebenen call () -Methode implementiert. In der Hauptmethode erstellen wir 10 FutureTask-Objekte. Jeder Objektkonstruktor hat ein Callable-Klassenobjekt als Argument. Dann wird das FutureTask-Objekt einer Thread-Instanz zugeordnet.
Daher erstellen wir indirekt einen Thread mit einem Callable-Schnittstellenobjekt.
import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; //create a class implementing Callable interface class CallableDemo implements Callable { //define call () method public Object call() throws Exception { Random generator = new Random(); Integer randomNumber = generator.nextInt(10); Thread.sleep(randomNumber * 1000); return randomNumber; } } public class Main { public static void main(String() args) throws Exception { // Array of FutureTask objects FutureTask() randomNumberTasks = new FutureTask(10); for (int i = 0; i <10; i++) { Callable callable = new CallableDemo(); // Create the FutureTask with Callable class randomNumberTasks(i) = new FutureTask(callable); // create thread with FutureTask Thread t = new Thread(randomNumberTasks(i)); //start the thread t.start(); } System.out.println('The contents of FutureTask objects:'); for (int i = 0; i < 10; i++) { // get() contents of FutureTask System.out.print(randomNumberTasks(i).get() + ' '); } } }
Ausgabe
Wie im obigen Programm gezeigt, generiert die call () -Methode von Callable, die in der Klasse, die Callable implementiert, überschrieben wird, Zufallszahlen. Sobald der Thread gestartet ist, werden diese Zufallszahlen angezeigt.
Außerdem verwenden wir FutureTask-Objekte in der Hauptfunktion. Da die Future-Schnittstelle implementiert wird, müssen die Ergebnisse nicht in den Thread-Objekten gespeichert werden. Ebenso können wir die Aufgabe abbrechen, prüfen, ob sie ausgeführt wird oder abgeschlossen ist, und das Ergebnis auch mithilfe des FutureTask-Objekts abrufen.
ReentrantLock In Java
Wir haben die Thread-Synchronisation mit dem synchronisierten Schlüsselwort in unserem letzten Tutorial ausführlich besprochen. Die Verwendung des synchronisierten Wortes für die Thread-Synchronisation ist die grundlegende Methode und etwas starr.
Mit dem synchronisierten Schlüsselwort kann ein Thread nur einmal gesperrt werden. Nachdem ein Thread den synchronisierten Block verlassen hat, übernimmt der nächste Thread die Sperre. Es gibt keine Warteschlange. Diese Probleme können dazu führen, dass ein anderer Thread ausfällt, da er möglicherweise längere Zeit nicht auf die Ressourcen zugreifen kann.
Um diese Probleme zu beheben, benötigen wir eine flexible Methode zum Synchronisieren der Threads. Die 'Reentrant Locks' ist diese Methode in Java, die eine Synchronisation mit weitaus größerer Flexibilität bietet.
Die Klasse 'ReentrantLock' implementiert Reentrant-Sperren und ist Teil des Pakets 'import java.util.concurrent.locks'. Die ReentrantLock-Klasse bietet die Methodensynchronisation für den Zugriff auf gemeinsam genutzte Ressourcen. Die Klassen verfügen auch über die Sperr- und Entsperrmethoden zum Sperren / Entsperren von Ressourcen, wenn Threads darauf zugreifen.
Eine Besonderheit von ReentrantLock ist, dass der Thread die gemeinsam genutzte Ressource mit ReentrantLock mehrmals sperren kann. Es bietet eine Haltezahl, die auf eins gesetzt wird, wenn der Thread die Ressource sperrt.
Der Thread kann vor dem Entsperren erneut auf die Ressource zugreifen und darauf zugreifen. Jedes Mal, wenn der Thread über die Reentrant-Sperre auf die Ressource zugreift, wird die Haltezahl um eins erhöht. Bei jedem Entsperren wird die Haltezahl um eins verringert.
Wenn die Haltezahl 0 erreicht, wird die gemeinsam genutzte Ressource entsperrt.
Die ReentrantLock-Klasse stellt auch einen Fairness-Parameter bereit, bei dem es sich um einen booleschen Wert handelt, der mit dem Konstruktor der Sperre übergeben werden kann. Wenn der Fairness-Parameter auf true gesetzt ist, wird die Sperre immer dann an den am längsten wartenden Thread übergeben, wenn ein Thread die Sperre aufhebt. Dies verhindert Hunger.
Die Wiedereintrittsschlösser können wie folgt verwendet werden:
return_type method_name() { reentrantlock.lock(); try { //Do some work } catch(Exception e) { e.printStackTrace(); } finally { reentrantlock.unlock(); } }
Beachten Sie, dass sich die Unlock-Anweisung für ReentrantLock immer im finally-Block befindet. Dies garantiert, dass die Sperre auch dann aufgehoben wird, wenn eine Ausnahme ausgelöst wird.
Implementieren wir ein Java-Programm, um ReentrantLock zu verstehen.
import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.*; import java.util.concurrent.locks.ReentrantLock; //thread class that implements Runnable interface class ThreadClass implements Runnable { String task_name; //define ReentrantLock object ReentrantLock thrd_lck; //ThreadClass constructor initialized lock and task name public ThreadClass(ReentrantLock r_lock, String t_name) { thrd_lck = r_lock; task_name = t_name; } //thread run () method public void run() { boolean bool_val = false; while (!bool_val) { //check for Outer Lock boolean tryLock_val = thrd_lck.tryLock(); // if lock is free, do the following if(tryLock_val) { try { for(int i=0;i<=6;i++) { if(i>=2) { thrd_lck.lock(); Thread thread_one = new Thread(); System.out.println('Thread Created.....'); if(i==3) { thread_one.setName('Maint Thread2'); System.out.println('Thread Created.....'); } } if(i==4) thrd_lck.unlock(); break; } System.out.println('ReentrantLock=>Is locked after sleep(1500) : ' + thrd_lck.isLocked()); System.out.println('Work done for task : ' + task_name ); bool_val = true; } catch(Exception e) { e.printStackTrace(); } } } } } public class Main { public static void main(String() args) { //define ReentrantLock lock object and service pool ReentrantLock reentrant_lock = new ReentrantLock(); ExecutorService pool = Executors.newFixedThreadPool(2); //create thread instance and pass lock and task name Runnable worker_thread = new ThreadClass(reentrant_lock, 'ThreadJob'); //execute the thread in exec pool pool.execute(worker_thread); //shut down the pool pool.shutdown(); } }
Ausgabe
Im obigen Programm haben wir einen Thread erstellt und ReentrantLock dafür verwendet. Mit ReentrantLock kann auf die gemeinsam genutzte Ressource zugegriffen werden.
Semaphor In Java
Die nächste Methode zur Thread-Synchronisation ist die Verwendung von Semaphore. Mit diesem als Semaphor bezeichneten Konstrukt wird der Zugriff auf eine gemeinsam genutzte Ressource über einen Zähler gesteuert. Signale werden zwischen den Threads gesendet, damit wir den kritischen Abschnitt schützen und auch verpasste Signale vermeiden können.
Ein Semaphor kann als eine Variable definiert werden, die zum Verwalten gleichzeitiger Prozesse durch Synchronisieren dieser Prozesse verwendet wird. Semaphore werden auch verwendet, um den Zugriff auf die gemeinsam genutzte Ressource zu synchronisieren und dadurch eine Racebedingung zu vermeiden. Die Berechtigung, die einem Thread für den Zugriff auf die gemeinsam genutzte Ressource per Semaphor erteilt wird, wird auch als Berechtigung bezeichnet.
Je nachdem, welche Funktionen sie ausführen, können Semaphoren in zwei Typen unterteilt werden:
# 1) Binäres Semaphor: Ein binäres Semaphor wird verwendet, um gleichzeitige Prozesse zu synchronisieren und gegenseitigen Ausschluss zu implementieren. Ein binäres Semaphor nimmt nur zwei Werte an, d. H. 0 und 1.
# 2) Semaphor zählen: Das Zählsemaphor hat einen Wert, der die Anzahl der Prozesse angibt, die in den kritischen Abschnitt eintreten können. Zu jedem Zeitpunkt gibt der Wert die maximale Anzahl von Prozessen an, die in den kritischen Abschnitt eintreten.
Wie funktioniert ein Semaphor?
Die Arbeitsweise eines Semaphors kann in den folgenden Schritten zusammengefasst werden:
- Wenn die Semaphoranzahl> 0 ist, bedeutet dies, dass der Thread über eine Berechtigung zum Zugriff auf kritische Abschnitte verfügt, und die Anzahl wird dann dekrementiert.
- Andernfalls wird der Thread blockiert, bis die Genehmigung erworben wurde.
- Wenn der Thread mit dem Zugriff auf die gemeinsam genutzte Ressource fertig ist, wird die Berechtigung freigegeben und die Anzahl der Semaphore erhöht, sodass ein anderer Thread die obigen Schritte wiederholen und die Genehmigung erhalten kann.
Die obigen Schritte der Arbeit mit Semaphoren können im folgenden Flussdiagramm zusammengefasst werden.
In Java müssen wir unser Semaphor nicht implementieren, aber es bietet eine Semaphor Klasse, die die Semaphorfunktionalität implementiert. Die Semaphore-Klasse ist Teil der java.util.concurrent Paket.
Die Semaphore-Klasse bietet die folgenden Konstruktoren, mit denen wir ein Semaphore-Objekt erstellen können:
Semaphore (int num_value) Semaphore (int num_value, boolean how)
Hier,
num_value => Anfangswert der Zulassungsanzahl, der die Anzahl der Threads bestimmt, die auf die gemeinsam genutzte Ressource zugreifen können.
wie => Legt die Reihenfolge fest, in der den Threads Genehmigungen erteilt werden (how = true). Wenn wie = falsch, wird keine solche Reihenfolge befolgt.
Jetzt werden wir ein Java-Programm implementieren, das das Semaphor demonstriert, mit dem der Zugriff auf gemeinsam genutzte Ressourcen verwaltet und die Race-Bedingung verhindert wird.
import java.util.concurrent.*; //class for shared resource class SharedRes { static int count = 0; } class ThreadClass extends Thread { Semaphore sem; String threadName; public ThreadClass(Semaphore sem, String threadName) { super(threadName); this.sem = sem; this.threadName = threadName; } @Override public void run() { // Thread T1 processing if(this.getName().equals('T1')) { System.out.println('Start: ' + threadName); try { System.out.println(threadName + ' :waiting for a permit.'); // acquire the permit sem.acquire(); System.out.println(threadName + ':Acquired permit'); // access shared resource for(int i=0; i <5; i++) { SharedRes.count++; System.out.println(threadName + ': ' + SharedRes.count); Thread.sleep(10); } } catch (InterruptedException exc) { System.out.println(exc); } // Release the permit. System.out.println(threadName + ':Released the permit'); sem.release(); } // Thread T2 processing else { System.out.println('Start: ' + threadName); try { System.out.println(threadName + ':waiting for a permit.'); // acquire the lock sem.acquire(); System.out.println(threadName + ':Acquired permit'); // process the shared resource for(int i=0; i < 5; i++) { SharedRes.count--; System.out.println(threadName + ': ' + SharedRes.count); Thread.sleep(10); } } catch (InterruptedException exc) { System.out.println(exc); } // Release the permit. System.out.println(threadName + ':Released the permit.'); sem.release(); } } } public class Main { public static void main(String args()) throws InterruptedException { //create Semaphore=> #permits = 1 Semaphore sem = new Semaphore(1); // Create thread instances T1 & T2 //T1=> Increments the count; T2=> Decrements the count ThreadClass thread1 = new ThreadClass(sem, 'T1'); ThreadClass thread2 = new ThreadClass(sem, 'T2'); // start T1 & T2 thread1.start(); thread2.start(); // Wait T1 & T2 thread1.join(); thread2.join(); System.out.println('count: ' + SharedRes.count); // display final count. } }
Ausgabe
Dieses Programm hat eine Klasse für die gemeinsam genutzte Ressource deklariert. Es deklariert auch eine Thread-Klasse, in der wir eine Semaphorvariable haben, die im Klassenkonstruktor initialisiert wird.
In der überschriebenen run () -Methode der Thread-Klasse wird die Thread-Instanz verarbeitet, in der der Thread die Berechtigung abruft, auf eine gemeinsam genutzte Ressource zugreift und dann die Berechtigung freigibt.
In der Hauptmethode haben wir zwei Thread-Instanzen deklariert. Beide Threads werden dann gestartet und warten dann mit der Join-Methode. Schließlich wird die Anzahl angezeigt, d. H. 0, was anzeigt, dass beide Threads mit der gemeinsam genutzten Ressource fertig sind.
Fork And Join In Java
Das Fork / Join-Framework wurde erstmals in Java 7 eingeführt. Dieses Framework besteht aus Tools, die die parallele Verarbeitung beschleunigen können. Es verwendet alle verfügbaren Prozessorkerne im System und schließt die Aufgabe ab. Das Fork / Join-Framework verwendet den Divide and Conquer-Ansatz.
Die Grundidee hinter dem Fork / Join-Framework besteht darin, dass das erste Framework 'Forks', d. H. Die Aufgabe rekursiv in kleinere einzelne Unteraufgaben aufteilt, bis die Aufgaben atomar sind, so dass sie asynchron ausgeführt werden können.
Danach werden die Aufgaben 'verbunden', d. H. Alle Unteraufgaben werden rekursiv zu einer einzelnen Aufgabe oder einem Rückgabewert zusammengefügt.
Das Fork / Join-Framework verfügt über einen Thread-Pool, der als 'ForkJoinPool' bezeichnet wird. Dieser Pool verwaltet die Worker-Threads vom Typ 'ForkJoinWorkerThread' und bietet so eine effektive Parallelverarbeitung.
ForkJoinPool verwaltet die Arbeitsthreads und hilft uns auch dabei, Informationen über die Leistung und den Status des Threadpools abzurufen. Der ForkJoinPool ist eine Implementierung des oben diskutierten 'ExecutorService'.
Im Gegensatz zu Arbeitsthreads erstellt der ForkJoinPool nicht für jede Unteraufgabe einen eigenen Thread. Jeder Thread im ForkJoinPool behält seine Deque (Double-Ended Queue) zum Speichern von Aufgaben bei.
Die Deque fungiert als Workload-Balancing des Threads und tut dies mithilfe eines unten beschriebenen 'Work-Stealing-Algorithmus'.
Algorithmus zum Stehlen von Arbeit
Wir können den Work-Stealing-Algorithmus in einfachen Worten definieren als 'Wenn ein Thread frei ist,' stehlen 'Sie die Arbeit von ausgelasteten Threads.'
Ein Worker-Thread erhält die Aufgaben immer von seiner Deque. Wenn alle Aufgaben in der Deque erschöpft sind und die Deque leer ist, übernimmt der Worker-Thread eine Aufgabe vom Ende einer anderen Deque oder aus der 'globalen Eingabewarteschlange'.
Auf diese Weise wird die Möglichkeit, dass Threads um Aufgaben konkurrieren, minimiert und auch die Häufigkeit, mit der der Thread nach Arbeit suchen muss, verringert. Dies liegt daran, dass der Thread bereits den größten Teil der verfügbaren Arbeit hat und ihn beendet hat.
Wie können wir den ForkJoinPool in einem Programm verwenden?
Die allgemeine Definition von ForkJoinPool lautet wie folgt:
public class ForkJoinPool extends AbstractExecutorService
Die Klasse ForkJoinPool ist Teil des Pakets 'java.util.concurrent'.
In Java 8 erstellen wir eine Instanz des ForkJoinPool mit seiner statischen Methode 'common-pool ()', die einen Verweis auf den allgemeinen Pool oder den Standard-Thread-Pool bereitstellt.
ForkJoinPool commonPool = ForkJoinPool.commonPool ();
In Java 7 erstellen wir eine ForkJoinPool-Instanz und weisen sie wie unten gezeigt dem Feld der Dienstprogrammklasse zu.
public static ForkJoinPool forkJoinPool = new ForkJoinPool(2);
Die obige Definition gibt an, dass der Pool eine Parallelitätsstufe von 2 hat, so dass der Pool 2 Prozessorkerne verwendet.
Systemüberwachungstools für Windows 10
Um auf den obigen Pool zuzugreifen, können wir die folgende Erklärung abgeben.
ForkJoinPool forkJoinPool = PoolUtil.forkJoinPool;
Der Basistyp für ForkJoinPool-Aufgaben ist 'ForkJoinTask'. Wir sollten eine seiner Unterklassen erweitern, d. H. Für ungültige Aufgaben die RecursiveAction und für Aufgaben, die einen Wert zurückgeben, die RecursiveTask. Beide erweiterten Klassen bieten eine abstrakte Methode compute (), in der wir die Logik der Aufgabe definieren.
Im Folgenden finden Sie ein Beispiel zur Demonstration des ForkJoinPool.
import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; //class declaration for ForkJoinPool tasks class FJPoolTask extends RecursiveAction { private long Load = 0; public FJPoolTask(long Load) { this.Load = Load; } @Override protected void compute() { //if threshold is reached, break tasks into smaller tasks List subtasks = new ArrayList(); subtasks.addAll(createSubtasks()); for(RecursiveAction subtask : subtasks){ subtask.fork(); } } //create subtasks private List createSubtasks() { List sub_tasks =new ArrayList(); FJPoolTask sub_task1 = new FJPoolTask(this.Load / 2); FJPoolTask sub_task2 = new FJPoolTask(this.Load / 2); FJPoolTask sub_task3 = new FJPoolTask(this.Load / 2); sub_tasks.add(sub_task1); sub_tasks.add(sub_task2); sub_tasks.add(sub_task3); return sub_tasks; } } public class Main { public static void main(final String() arguments) throws InterruptedException { //get count of available processors int proc = Runtime.getRuntime().availableProcessors(); System.out.println('Processors available:' +proc); //declare forkJoinPool ForkJoinPool Pool = ForkJoinPool.commonPool(); System.out.println(' Active Threads (Before invoke):' +Pool.getActiveThreadCount()); //Declare ForkJoinPool task object FJPoolTask t = new FJPoolTask(400); //submit the tasks to the pool Pool.invoke(t); System.out.println(' Active Threads (after invoke):' +Pool.getActiveThreadCount()); System.out.println('Common Pool Size :' +Pool.getPoolSize()); } }
Ausgabe
Im obigen Programm finden wir die Anzahl der aktiven Threads im System vor und nach dem Aufruf der Methode 'invoke ()'. Die invoke () -Methode wird verwendet, um die Aufgaben an den Pool zu senden. Wir finden auch die Anzahl der verfügbaren Prozessorkerne im System.
Häufig gestellte Fragen
F # 1) Was ist Java Util Concurrent?
Antworten: Das Paket 'java.util.concurrent' besteht aus einer Reihe von Klassen und Schnittstellen, die von Java bereitgestellt werden, um die Entwicklung gleichzeitiger (Multithread-) Anwendungen zu erleichtern. Mit diesem Paket können wir die Schnittstelle und Klassen sowie APIs direkt verwenden, ohne unsere Klassen schreiben zu müssen.
F # 2) Welche der folgenden Implementierungen sind gleichzeitig in der Datei java.util vorhanden? gleichzeitiges Paket?
Antworten: Auf hoher Ebene enthält das Paket java.util.concurrent Dienstprogramme wie Executors, Synchronizers, Queues, Timings und Concurrent Collections.
F # 3) Was ist Future Java?
Antworten: Ein Future-Objekt (java.util.concurrent.Future) wird verwendet, um das Ergebnis zu speichern, das von einem Thread zurückgegeben wird, wenn die Callable-Schnittstelle implementiert wird.
F # 4) Was ist in Java threadsicher?
Antworten: Ein threadsicherer Code oder eine Klasse in Java ist ein Code oder eine Klasse, die problemlos in einer Umgebung mit mehreren Threads oder gleichzeitig verwendet werden kann und zu erwarteten Ergebnissen führt.
F # 5) Was ist die synchronisierte Sammlung in Java?
Antworten: Eine synchronisierte Sammlung ist eine thread-sichere Sammlung. Die Methode synchronized collection () der Klasse java.util.Collections gibt eine synchronisierte (thread-sichere) Sammlung zurück.
Fazit
Mit diesem Tutorial haben wir das Thema Multithreading und Parallelität in Java abgeschlossen. Wir haben Multithreading in unseren vorherigen Tutorials ausführlich besprochen. Hier haben wir die Parallelität und die Implementierung im Zusammenhang mit Parallelität und Multithreading erörtert, die Teil des Pakets java.util.concurrent sind.
Wir haben zwei weitere Synchronisationsmethoden besprochen, Semaphoren und ReentrantLock. Wir haben auch den ForkJoinPool besprochen, mit dem die Aufgaben ausgeführt werden, indem sie in einfachere Aufgaben unterteilt und schließlich dem Ergebnis hinzugefügt werden.
Das Paket java.util.concurrent unterstützt auch das Executor-Framework und Executoren, die uns beim Ausführen von Threads helfen. Wir haben auch die Implementierung des Thread-Pools erörtert, die aus wiederverwendbaren Threads besteht, die nach Abschluss der Ausführung an den Pool zurückgegeben werden.
Wir haben eine andere Schnittstelle ähnlich Runnable besprochen, die uns auch dabei hilft, ein Ergebnis aus dem Thread und dem Future-Objekt zurückzugeben, das zum Speichern des erhaltenen Thread-Ergebnisses verwendet wird.
=> Sehen Sie sich hier die einfache Java-Schulungsreihe an.
Literatur-Empfehlungen
- Thread.Sleep () - Thread Sleep () -Methode in Java mit Beispielen
- Java-Bereitstellung: Erstellung und Ausführung einer Java-JAR-Datei
- Java-Grundlagen: Java-Syntax, Java-Klasse und Java-Kernkonzepte
- Java Virtual Machine: Wie JVM beim Ausführen von Java-Anwendungen hilft
- Zugriffsmodifikatoren in Java - Tutorial mit Beispielen
- Java synchronisiert: Was ist Thread-Synchronisation in Java?
- JAVA-Tutorial für Anfänger: Über 100 praktische Java-Video-Tutorials
- Java Integer und Java BigInteger Klasse mit Beispielen