Kursinhalt
Multithreading in Java
Multithreading in Java
BlockingQueue und Seine Implementierungen
Grundlegende BlockingQueue-Implementierungen
Wir werden nicht jede Realisierung im Detail durchgehen, da es viel Zeit in Anspruch nehmen würde und es unwahrscheinlich ist, dass Sie sie alle benötigen. Ich werde über die allgemeinen Konzepte und welche Konstruktoren sie haben, sprechen.
Beispiel aus dem echten Leben
Stellen Sie sich eine Fabrik vor, in der ein Thread, der Produzent, Teile erstellt und ein anderer Thread, der Konsument, diese verarbeitet. Der Produzent legt die Teile in eine Warteschlange, während der Konsument sie aus der Warteschlange abruft und verarbeitet. Wenn die Warteschlange keine Teile mehr hat, wartet der Konsument darauf, dass der Produzent mehr hinzufügt. Umgekehrt wartet der Produzent, wenn die Warteschlange voll ist, darauf, dass der Konsument Platz schafft.
Hinweis
Etwas weiter unten werden wir diese Aufgabe im Code implementieren.
Unterschiede zu anderen Sammlungstypen
Die BlockingQueue
bietet automatische Synchronisation, die den Thread-Zugriff auf die Warteschlange verwaltet, ohne dass eine manuelle Synchronisation erforderlich ist. Sie unterstützt auch blockierende Operationen zum Hinzufügen und Abrufen von Elementen, eine Funktion, die in anderen Sammlungen wie ArrayList
oder LinkedList
nicht vorhanden ist.
Implementierungen von BlockingQueue
ArrayBlockingQueue
: Eine größenbeschränkte Warteschlange, die ein Array zur Speicherung von Elementen verwendet.
Main
// Constructor with fixed capacity BlockingQueue<String> queue1 = new ArrayBlockingQueue<>(5); // Constructor with fixed capacity and fair access BlockingQueue<String> queue2 = new ArrayBlockingQueue<>(5, true); // Constructor with fixed capacity and initial collection of elements Collection<String> initialElements = java.util.Arrays.asList("One", "Two", "Three"); BlockingQueue<String> queue3 = new ArrayBlockingQueue<>(5, false, initialElements);
Erklärung
Der
true
Parameter ermöglicht eine faire Zugriffspolitik, indem er eine FIFO-Reihenfolge für den Thread-Zugriff bereitstellt.
LinkedBlockingQueueue
: Eine Warteschlange basierend auf verketteten Knoten, die beschränkt oder unbeschränkt sein kann.
Main
// Constructor without capacity bounds BlockingQueue<String> queue1 = new LinkedBlockingQueue<>(); // Constructor with fixed capacity BlockingQueue<String> queue2 = new LinkedBlockingQueue<>(5); // Constructor with initial collection of elements Collection<String> initialElements = java.util.Arrays.asList("One", "Two", "Three"); BlockingQueue<String> queue3 = new LinkedBlockingQueue<>(initialElements);
PriorityBlockingQueue
: Eine unbegrenzte priorisierte Warteschlange, bei der Elemente gemäß ihrer natürlichen Reihenfolge oder wie durch einen Comparator angegeben abgerufen werden.
Main
// Constructor without initial capacity (default is 11) BlockingQueue<Integer> queue1 = new PriorityBlockingQueue<>(); // Constructor with initial capacity BlockingQueue<Integer> queue2 = new PriorityBlockingQueue<>(5); // Constructor with initial capacity and comparator Comparator<Integer> comparator = Integer::compareTo; BlockingQueue<Integer> queue3 = new PriorityBlockingQueue<>(5, comparator); // Constructor with initial collection of elements Collection<Integer> initialElements = java.util.Arrays.asList(1, 3, 2); BlockingQueue<Integer> queue4 = new PriorityBlockingQueue<>(initialElements)
DelayQueue
: Eine verzögerte Warteschlange, bei der Elemente erst nachdem ihre Verzögerung abgelaufen ist, abgerufen werden können.
DelayedElement
DelayQueueConstructors
class DelayedElement implements Delayed { private final long expirationTime; // The time when the element will be available public DelayedElement(long delay, TimeUnit unit) { this.expirationTime = System.currentTimeMillis() + unit.toMillis(delay); } @Override public long getDelay(TimeUnit unit) { long delay = expirationTime - System.currentTimeMillis(); // Calculate the remaining delay return unit.convert(delay, TimeUnit.MILLISECONDS); // Convert the delay to the specified time unit } @Override public int compareTo(Delayed o) { return Long.compare(this.expirationTime, ((DelayedElement) o).expirationTime); } }
Dieser Code demonstriert die Verwendung der DelayedElement
-Klasse, die das Delayed
-Interface implementiert, und der DelayQueue
-Verzögerungswarteschlange in Java. Die DelayedElement
-Klasse definiert eine getDelay
-Methode, um die verbleibende Verzögerungszeit zu berechnen, und eine compareTo
-Methode, um Objekte basierend auf der Verzögerungsablaufzeit zu vergleichen.
Die main
-Methode erstellt zwei Warteschlangen: queue1
, eine leere Verzögerungswarteschlange, und queue2
, eine Warteschlange, die mit Elementen initialisiert ist, die eine Verzögerung von 5 bzw. 1 Sekunde haben.
Die Elemente in DelayQueueue
werden verfügbar zur Abholung, nachdem die angegebene Verzögerungszeit abgelaufen ist.
SynchronousQueueue
: Eine Warteschlange ohne Kapazität, bei der jede Einfügeoperation auf die entsprechende Extraktionsoperation warten muss und umgekehrt.
Main
// Constructor without fair access BlockingQueue<String> queue1 = new SynchronousQueue<>(); // Constructor with fair access BlockingQueue<String> queue2 = new SynchronousQueue<>(true);
Die Hauptmethoden der BlockingQueue:
Hinzufügen von Elementen:
Die void put(E e)
Methode fügt ein Element in die Warteschlange ein und blockiert den Thread, wenn die Warteschlange voll ist. Alternativ versucht die boolean offer(E e, long timeout, TimeUnit unit)
Methode, ein Element in die Warteschlange einzufügen und wartet die angegebene Zeit, wenn die Warteschlange voll ist.
Main
public class BlockingQueueExample { public static void main(String[] args) { BlockingQueue<String> queue = new ArrayBlockingQueue<>(2); try { queue.put("Element 1"); // Insert the first element, no blocking. queue.put("Element 2"); // Insert the second element, no blocking. // Try to add the third element with a 2-second timeout. // Since the queue is full, it will wait for 2 seconds. boolean success = queue.offer("Element 3", 2, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } }
Dieses Beispiel zeigt das Einfügen von zwei Elementen in eine BlockingQueue
ohne Blockierung, gefolgt von einem Versuch, ein drittes Element mit einem 2-Sekunden-Timeout mithilfe der offer()
-Methode hinzuzufügen, die wartet, wenn die Warteschlange voll ist.
Elementabruf:
Die E take()
Methode ruft ein Element aus der Warteschlange ab und gibt es zurück, wobei der Thread blockiert wird, wenn die Warteschlange leer ist. Alternativ versucht die E poll(long timeout, TimeUnit unit)
Methode, ein Element aus der Warteschlange abzurufen und wartet die angegebene Zeit, wenn die Warteschlange leer ist.
Main
public class BlockingQueueRetrievalExample { public static void main(String[] args) { BlockingQueue<String> queue = new ArrayBlockingQueue<>(2); try { // Adding elements to the queue queue.put("Element 1"); queue.put("Element 2"); // Retrieve and remove the first element, no blocking since the queue is not empty String item1 = queue.take(); // Returns "Element 1" // Attempt to retrieve and remove the next element with a 2-second timeout String item2 = queue.poll(2, TimeUnit.SECONDS); // Returns "Element 2" // Attempt to retrieve an element when the queue is empty, this will block for 2 seconds String item3 = queue.poll(2, TimeUnit.SECONDS); // Returns `null` after timeout } catch (InterruptedException e) { e.printStackTrace(); } } }
Dieser Code fügt zwei Elemente zu einer BlockingQueue
hinzu, ruft und entfernt das erste Element sofort, versucht, das nächste Element mit einem 2-Sekunden-Timeout abzurufen, und versucht schließlich, ein Element aus einer leeren Warteschlange abzurufen, was nach dem Timeout zu einem null
führt.
Überprüfen und Entfernen von Elementen:
Die boolean remove(Object o)
Methode entfernt das angegebene Element aus der Warteschlange, wenn es vorhanden ist. Andererseits überprüft die boolean contains(Object o)
Methode, ob das angegebene Element in der Warteschlange vorhanden ist, ohne es zu entfernen.
Main
public class BlockingQueueCheckRemoveExample { public static void main(String[] args) { BlockingQueue<String> queue = new ArrayBlockingQueue<>(2); try { // Adding elements to the queue queue.put("Element 1"); queue.put("Element 2"); // Check if "Element 1" is in the queue, should return `true` boolean containsElement1 = queue.contains("Element 1"); // true // Remove "Element 1" from the queue, should return `true` boolean removedElement1 = queue.remove("Element 1"); // true // Check if "Element 1" is still in the queue, should return `false` boolean containsElement1AfterRemoval = queue.contains("Element 1"); // false // Try to remove an element that is not in the queue, should return `false` boolean removedElement3 = queue.remove("Element 3"); // false } catch (InterruptedException e) { e.printStackTrace(); } } }
Dieser Code fügt zwei Elemente zu einer BlockingQueue
hinzu, überprüft das Vorhandensein von "Element 1", entfernt es, überprüft erneut, um seine Entfernung zu bestätigen, und versucht dann, ein nicht vorhandenes Element zu entfernen.
Überprüft den Zustand der Warteschlange:
Die int size()
Methode gibt die Anzahl der Elemente zurück, die sich derzeit in der Warteschlange befinden. Um festzustellen, ob die Warteschlange leer ist, können Sie die boolean isEmpty()
Methode verwenden, die überprüft, ob die Warteschlange keine Elemente hat. Für Warteschlangen mit einer festen Kapazität bietet die int remainingCapacity()
Methode die Anzahl der verbleibenden verfügbaren Plätze in der Warteschlange.
Main
public class BlockingQueueCapacityExample { public static void main(String[] args) { BlockingQueue<String> queue = new ArrayBlockingQueue<>(3); try { // Adding elements to the queue queue.put("Element 1"); queue.put("Element 2"); // Get the number of elements in the queue int currentSize = queue.size(); // 2 // Check if the queue is empty boolean isQueueEmpty = queue.isEmpty(); // false // Get the remaining capacity in the queue int remainingSpace = queue.remainingCapacity(); // 1 // Add another element to fill the queue queue.put("Element 3"); // Check the size and remaining capacity after adding the third element currentSize = queue.size(); // 3 remainingSpace = queue.remainingCapacity(); // 0 } catch (InterruptedException e) { e.printStackTrace(); } } }
Dieser Code fügt Elemente hinzu zu einer BlockingQueue
, überprüft die aktuelle Größe, verifiziert, ob die Warteschlange leer ist, und bestimmt die verbleibende Kapazität, dann aktualisiert er diese Werte, nachdem die Warteschlange vollständig gefüllt wurde.
Ein reales Beispiel im Code umsetzen
😭 Einschränkungen
Eine wesentliche Einschränkung ist die Leistung: Aufgrund der Sperroperationen kann die Leistung im Vergleich zu nicht-synchronisierten Sammlungen reduziert werden. Darüber hinaus können Ressourcen ein Problem darstellen, da große Warteschlangen mehr Speicher und CPU-Zeit benötigen, um die Sperren und Synchronisationsprozesse zu handhaben.
💪 Vorteile
Auf der positiven Seite ist das System sicher im Multithreading, bietet sichere Kommunikation zwischen Threads, ohne dass eine manuelle Synchronisationsverwaltung erforderlich ist. Es vereinfacht auch den Code, indem es komplexe Synchronisations- und Blockierungskonstrukte vermeidet. Darüber hinaus bedeutet die Flexibilität der verschiedenen BlockingQueue
-Implementierungen, dass sie für verschiedene Nutzungsszenarien geeignet sein können.
1. Was ist eine BlockingQueue in Java?
2. Was sind die Hauptmethoden von BlockingQueue, die einen Thread blockieren?
3. Wofür ist BlockingQueue in Multithread-Anwendungen nützlich?
Danke für Ihr Feedback!