Blockingqueue und Deren Implementierungen
Grundlegende BlockingQueue-Implementierungen
Wir werden nicht jede Realisierung im Detail durchgehen, da dies sehr zeitaufwändig wäre und Sie wahrscheinlich nicht alle benötigen werden. Ich werde die allgemeinen Konzepte und die vorhandenen Konstruktoren erläutern.
Beispiel aus der Praxis
Stellen Sie sich eine Fabrik vor, in der ein Thread, der Producer, Teile herstellt, und ein anderer Thread, der Consumer, diese verarbeitet. Der Producer legt die Teile in eine Warteschlange ein, während der Consumer sie aus der Warteschlange entnimmt und verarbeitet. Wenn die Warteschlange leer ist, wartet der Consumer darauf, dass der Producer neue Teile hinzufügt. Umgekehrt wartet der Producer, wenn die Warteschlange voll ist, bis der Consumer Platz schafft.
Etwas weiter unten werden wir diese Aufgabe im Code implementieren.
Unterschiede zu anderen Sammlungstypen
Die BlockingQueue bietet automatisierte Synchronisation und verwaltet den Thread-Zugriff auf die Warteschlange, ohne dass eine manuelle Synchronisation erforderlich ist. Sie unterstützt außerdem blockierende Operationen zum Hinzufügen und Abrufen von Elementen, eine Funktion, die in anderen Sammlungen wie ArrayList oder LinkedList nicht vorhanden ist.
BlockingQueue-Implementierungen
ArrayBlockingQueue: Eine größenbeschränkte Warteschlange, die ein Array zur Speicherung der Elemente verwendet.
Main.java
123456789// 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);
Der Parameter true aktiviert eine faire Zugriffspolitik, indem er eine FIFO-Reihenfolge für den Thread-Zugriff bereitstellt.
LinkedBlockingQueueue: Eine auf verketteten Knoten basierende Warteschlange, die beschränkt oder unbeschränkt sein kann.
Main.java
123456789// 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 entsprechend ihrer natürlichen Reihenfolge oder wie durch einen Comparator angegeben entnommen werden.
Main.java
12345678910111213// 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 nach Ablauf ihrer Verzögerung abgerufen werden können.
DelayedElement.java
DelayQueueConstructors.java
123456789101112131415161718class 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 veranschaulicht die Verwendung der DelayedElement-Klasse, die das Delayed-Interface implementiert, sowie der verzögerten Warteschlange DelayQueue in Java. Die Klasse DelayedElement definiert eine getDelay-Methode zur Berechnung der verbleibenden Verzögerungszeit und eine compareTo-Methode zum Vergleichen von Objekten basierend auf dem Ablaufzeitpunkt der Verzögerung.
Die main-Methode erstellt zwei Warteschlangen: queue1, eine leere verzögerte Warteschlange, und queue2, eine Warteschlange, die mit Elementen initialisiert wird, die eine Verzögerung von 5 bzw. 1 Sekunde haben.
Die Elemente in der DelayQueueue werden erst nach Ablauf der angegebenen Verzögerungszeit zum Abruf verfügbar.
SynchronousQueueue: Eine Warteschlange ohne Kapazität, bei der jede Einfügeoperation auf die entsprechende Entnahmeoperation warten muss und umgekehrt.
Main.java
12345// 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 Methode void put(E e) fügt ein Element in die Warteschlange ein und blockiert den Thread, wenn die Warteschlange voll ist. Alternativ versucht die Methode boolean offer(E e, long timeout, TimeUnit unit), ein Element zur Warteschlange hinzuzufügen und wartet für die angegebene Zeit, falls die Warteschlange voll ist.
Main.java
1234567891011121314151617public 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 dem Versuch, ein drittes Element mit einem 2-Sekunden-Timeout mithilfe der offer()-Methode hinzuzufügen, wobei gewartet wird, falls die Warteschlange voll ist.
Elemententnahme:
Die Methode E take() entnimmt und gibt ein Element aus der Warteschlange zurück und blockiert den Thread, falls die Warteschlange leer ist. Alternativ versucht die Methode E poll(long timeout, TimeUnit unit), ein Element aus der Warteschlange zu entnehmen und wartet für die angegebene Zeit, falls die Warteschlange leer ist.
Main.java
1234567891011121314151617181920212223public 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 das erste Element sofort ab und entfernt es, 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 null führt.
Überprüfen und Entfernen von Elementen:
Die Methode boolean remove(Object o) entfernt das angegebene Element aus der Warteschlange, sofern es vorhanden ist. Im Gegensatz dazu prüft die Methode boolean contains(Object o), ob das angegebene Element in der Warteschlange vorhanden ist, ohne es zu entfernen.
Main.java
1234567891011121314151617181920212223242526public 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, prüft das Vorhandensein von "Element 1", entfernt dieses, überprüft erneut, um die Entfernung zu bestätigen, und versucht anschließend, ein nicht vorhandenes Element zu entfernen.
Abfrage des Zustands der Queue:
Die Methode int size() gibt die Anzahl der aktuell in der Queue befindlichen Elemente zurück. Um festzustellen, ob die Queue leer ist, kann die Methode boolean isEmpty() verwendet werden, die prüft, ob keine Elemente vorhanden sind. Für Queues mit fester Kapazität liefert die Methode int remainingCapacity() die Anzahl der noch verfügbaren Plätze in der Queue.
Main.java
123456789101112131415161718192021222324252627282930public 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 zu einer BlockingQueue hinzu, prüft die aktuelle Größe, überprüft, ob die Warteschlange leer ist, und ermittelt die verbleibende Kapazität. Anschließend werden diese Werte nach vollständigem Füllen der Warteschlange aktualisiert.
Umsetzung eines Praxisbeispiels im Code
😭 Einschränkungen
Eine wesentliche Einschränkung ist die Leistung: Aufgrund der erforderlichen Sperroperationen kann die Performance im Vergleich zu nicht-synchronisierten Collections verringert sein. Zusätzlich können Ressourcen problematisch werden, da große Warteschlangen mehr Speicher und CPU-Zeit benötigen, um die Sperren und Synchronisationsprozesse zu verwalten.
💪 Vorteile
Auf der positiven Seite ist das System threadsicher, bietet eine sichere Kommunikation zwischen Threads ohne manuelles Synchronisationsmanagement und vereinfacht den Code, da komplexe Synchronisations- und Blockierungskonstrukte vermieden werden. Darüber hinaus ermöglicht die Flexibilität verschiedener BlockingQueue-Implementierungen, dass sie für unterschiedliche Anwendungsfälle geeignet sind.
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!
Fragen Sie AI
Fragen Sie AI
Fragen Sie alles oder probieren Sie eine der vorgeschlagenen Fragen, um unser Gespräch zu beginnen
What are the main differences between the BlockingQueue implementations?
Can you explain how the DelayQueue works in more detail?
How does the producer-consumer example work in code?
Awesome!
Completion rate improved to 3.33
Blockingqueue und Deren Implementierungen
Swipe um das Menü anzuzeigen
Grundlegende BlockingQueue-Implementierungen
Wir werden nicht jede Realisierung im Detail durchgehen, da dies sehr zeitaufwändig wäre und Sie wahrscheinlich nicht alle benötigen werden. Ich werde die allgemeinen Konzepte und die vorhandenen Konstruktoren erläutern.
Beispiel aus der Praxis
Stellen Sie sich eine Fabrik vor, in der ein Thread, der Producer, Teile herstellt, und ein anderer Thread, der Consumer, diese verarbeitet. Der Producer legt die Teile in eine Warteschlange ein, während der Consumer sie aus der Warteschlange entnimmt und verarbeitet. Wenn die Warteschlange leer ist, wartet der Consumer darauf, dass der Producer neue Teile hinzufügt. Umgekehrt wartet der Producer, wenn die Warteschlange voll ist, bis der Consumer Platz schafft.
Etwas weiter unten werden wir diese Aufgabe im Code implementieren.
Unterschiede zu anderen Sammlungstypen
Die BlockingQueue bietet automatisierte Synchronisation und verwaltet den Thread-Zugriff auf die Warteschlange, ohne dass eine manuelle Synchronisation erforderlich ist. Sie unterstützt außerdem blockierende Operationen zum Hinzufügen und Abrufen von Elementen, eine Funktion, die in anderen Sammlungen wie ArrayList oder LinkedList nicht vorhanden ist.
BlockingQueue-Implementierungen
ArrayBlockingQueue: Eine größenbeschränkte Warteschlange, die ein Array zur Speicherung der Elemente verwendet.
Main.java
123456789// 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);
Der Parameter true aktiviert eine faire Zugriffspolitik, indem er eine FIFO-Reihenfolge für den Thread-Zugriff bereitstellt.
LinkedBlockingQueueue: Eine auf verketteten Knoten basierende Warteschlange, die beschränkt oder unbeschränkt sein kann.
Main.java
123456789// 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 entsprechend ihrer natürlichen Reihenfolge oder wie durch einen Comparator angegeben entnommen werden.
Main.java
12345678910111213// 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 nach Ablauf ihrer Verzögerung abgerufen werden können.
DelayedElement.java
DelayQueueConstructors.java
123456789101112131415161718class 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 veranschaulicht die Verwendung der DelayedElement-Klasse, die das Delayed-Interface implementiert, sowie der verzögerten Warteschlange DelayQueue in Java. Die Klasse DelayedElement definiert eine getDelay-Methode zur Berechnung der verbleibenden Verzögerungszeit und eine compareTo-Methode zum Vergleichen von Objekten basierend auf dem Ablaufzeitpunkt der Verzögerung.
Die main-Methode erstellt zwei Warteschlangen: queue1, eine leere verzögerte Warteschlange, und queue2, eine Warteschlange, die mit Elementen initialisiert wird, die eine Verzögerung von 5 bzw. 1 Sekunde haben.
Die Elemente in der DelayQueueue werden erst nach Ablauf der angegebenen Verzögerungszeit zum Abruf verfügbar.
SynchronousQueueue: Eine Warteschlange ohne Kapazität, bei der jede Einfügeoperation auf die entsprechende Entnahmeoperation warten muss und umgekehrt.
Main.java
12345// 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 Methode void put(E e) fügt ein Element in die Warteschlange ein und blockiert den Thread, wenn die Warteschlange voll ist. Alternativ versucht die Methode boolean offer(E e, long timeout, TimeUnit unit), ein Element zur Warteschlange hinzuzufügen und wartet für die angegebene Zeit, falls die Warteschlange voll ist.
Main.java
1234567891011121314151617public 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 dem Versuch, ein drittes Element mit einem 2-Sekunden-Timeout mithilfe der offer()-Methode hinzuzufügen, wobei gewartet wird, falls die Warteschlange voll ist.
Elemententnahme:
Die Methode E take() entnimmt und gibt ein Element aus der Warteschlange zurück und blockiert den Thread, falls die Warteschlange leer ist. Alternativ versucht die Methode E poll(long timeout, TimeUnit unit), ein Element aus der Warteschlange zu entnehmen und wartet für die angegebene Zeit, falls die Warteschlange leer ist.
Main.java
1234567891011121314151617181920212223public 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 das erste Element sofort ab und entfernt es, 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 null führt.
Überprüfen und Entfernen von Elementen:
Die Methode boolean remove(Object o) entfernt das angegebene Element aus der Warteschlange, sofern es vorhanden ist. Im Gegensatz dazu prüft die Methode boolean contains(Object o), ob das angegebene Element in der Warteschlange vorhanden ist, ohne es zu entfernen.
Main.java
1234567891011121314151617181920212223242526public 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, prüft das Vorhandensein von "Element 1", entfernt dieses, überprüft erneut, um die Entfernung zu bestätigen, und versucht anschließend, ein nicht vorhandenes Element zu entfernen.
Abfrage des Zustands der Queue:
Die Methode int size() gibt die Anzahl der aktuell in der Queue befindlichen Elemente zurück. Um festzustellen, ob die Queue leer ist, kann die Methode boolean isEmpty() verwendet werden, die prüft, ob keine Elemente vorhanden sind. Für Queues mit fester Kapazität liefert die Methode int remainingCapacity() die Anzahl der noch verfügbaren Plätze in der Queue.
Main.java
123456789101112131415161718192021222324252627282930public 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 zu einer BlockingQueue hinzu, prüft die aktuelle Größe, überprüft, ob die Warteschlange leer ist, und ermittelt die verbleibende Kapazität. Anschließend werden diese Werte nach vollständigem Füllen der Warteschlange aktualisiert.
Umsetzung eines Praxisbeispiels im Code
😭 Einschränkungen
Eine wesentliche Einschränkung ist die Leistung: Aufgrund der erforderlichen Sperroperationen kann die Performance im Vergleich zu nicht-synchronisierten Collections verringert sein. Zusätzlich können Ressourcen problematisch werden, da große Warteschlangen mehr Speicher und CPU-Zeit benötigen, um die Sperren und Synchronisationsprozesse zu verwalten.
💪 Vorteile
Auf der positiven Seite ist das System threadsicher, bietet eine sichere Kommunikation zwischen Threads ohne manuelles Synchronisationsmanagement und vereinfacht den Code, da komplexe Synchronisations- und Blockierungskonstrukte vermieden werden. Darüber hinaus ermöglicht die Flexibilität verschiedener BlockingQueue-Implementierungen, dass sie für unterschiedliche Anwendungsfälle geeignet sind.
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!