BlockingQueue e le Sue Implementazioni
Implementazioni di base di BlockingQueue
Non esamineremo in dettaglio ogni realizzazione, poiché richiederebbe molto tempo e difficilmente saranno tutte necessarie. Verranno illustrati i concetti generali e i costruttori disponibili.
Esempio reale
Immaginare una fabbrica in cui un thread, il produttore, crea dei componenti, mentre un altro thread, il consumatore, li elabora. Il produttore inserisce i componenti in una coda, mentre il consumatore li preleva e li processa dalla coda. Se la coda si svuota, il consumatore attende che il produttore aggiunga altri componenti. Al contrario, se la coda è piena, il produttore attende che il consumatore liberi spazio.
Poco più sotto implementeremo questo compito nel codice.
Differenze rispetto ad altri tipi di collezioni
La BlockingQueue fornisce sincronizzazione automatica, gestendo l'accesso dei thread alla coda senza richiedere sincronizzazione manuale. Supporta inoltre operazioni di blocco per l'aggiunta e il recupero degli elementi, una funzionalità non presente in altre collezioni come ArrayList o LinkedList.
Implementazioni di BlockingQueue
ArrayBlockingQueue: Una coda a dimensione limitata che utilizza un array per memorizzare gli elementi.
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);
Il parametro true abilita una politica di accesso equa fornendo un ordine FIFO per l'accesso dei thread.
LinkedBlockingQueueue: una coda basata su nodi collegati che può essere limitata o illimitata.
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: Coda prioritaria illimitata in cui gli elementi vengono recuperati secondo il loro ordine naturale o come specificato da un comparatore.
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: Una coda ritardata in cui gli elementi possono essere recuperati solo dopo che il loro ritardo è scaduto.
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); } }
Questo codice dimostra l'utilizzo della classe DelayedElement, che implementa l'interfaccia Delayed, e della coda ritardata DelayQueue in Java. La classe DelayedElement definisce un metodo getDelay per calcolare il tempo di ritardo rimanente e un metodo compareTo per confrontare gli oggetti in base al tempo di scadenza del ritardo.
Il metodo main crea due code: queue1, una coda ritardata vuota, e queue2, una coda inizializzata con elementi che hanno rispettivamente un ritardo di 5 e 1 secondo.
Gli elementi in DelayQueueue diventano disponibili per il recupero dopo che è trascorso il tempo di ritardo specificato.
SynchronousQueueue: Una coda senza capacità, in cui ogni operazione di inserimento deve attendere la corrispondente operazione di estrazione e viceversa.
Main.java
12345// Constructor without fair access BlockingQueue<String> queue1 = new SynchronousQueue<>(); // Constructor with fair access BlockingQueue<String> queue2 = new SynchronousQueue<>(true);
I principali metodi di BlockingQueue:
Aggiunta di elementi:
Il metodo void put(E e) inserisce un elemento nella coda, bloccando il thread se la coda è piena. In alternativa, il metodo boolean offer(E e, long timeout, TimeUnit unit) tenta di aggiungere un elemento alla coda, attendendo per il tempo specificato se la coda è piena.
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(); } } }
Questo esempio mostra l'inserimento di due elementi in una BlockingQueue senza bloccare, seguito da un tentativo di aggiungere un terzo elemento con un timeout di 2 secondi utilizzando il metodo offer(), che attenderà se la coda è piena.
Recupero degli elementi:
Il metodo E take() recupera e restituisce un elemento dalla coda, bloccando il thread se la coda è vuota. In alternativa, il metodo E poll(long timeout, TimeUnit unit) tenta di recuperare un elemento dalla coda, attendendo per il tempo specificato se la coda è vuota.
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(); } } }
Questo codice aggiunge due elementi a una BlockingQueue, recupera e rimuove immediatamente il primo elemento, tenta di recuperare il successivo elemento con un timeout di 2 secondi e infine prova a recuperare un elemento da una coda vuota, che restituisce null dopo il timeout.
Verifica e rimozione degli elementi:
Il metodo boolean remove(Object o) rimuove l'elemento specificato dalla coda se presente. Al contrario, il metodo boolean contains(Object o) verifica se l'elemento specificato è presente nella coda senza rimuoverlo.
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(); } } }
Questo codice aggiunge due elementi a una BlockingQueue, verifica la presenza di "Element 1", lo rimuove, controlla nuovamente per confermare la sua rimozione e poi tenta di rimuovere un elemento inesistente.
Interrogazione dello Stato della Coda:
Il metodo int size() restituisce il numero di elementi attualmente presenti nella coda. Per determinare se la coda è vuota, è possibile utilizzare il metodo boolean isEmpty(), che verifica se la coda non contiene elementi. Per le code con capacità fissa, il metodo int remainingCapacity() fornisce il numero di spazi disponibili rimanenti nella coda.
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(); } } }
Questo codice aggiunge elementi a una BlockingQueue, controlla la dimensione attuale, verifica se la coda è vuota e determina la capacità residua, quindi aggiorna questi valori dopo aver riempito completamente la coda.
Realizzazione di un esempio reale in codice
😭 Limitazioni
Una delle principali limitazioni è la prestazione: a causa delle operazioni di locking coinvolte, le prestazioni possono risultare inferiori rispetto alle collezioni non sincronizzate. Inoltre, le risorse possono diventare un problema poiché code di grandi dimensioni richiedono più memoria e tempo CPU per gestire i lock e i processi di sincronizzazione.
💪 Vantaggi
Dal lato positivo, il sistema è sicuro nel multithreading, offrendo una comunicazione sicura tra i thread senza la necessità di una gestione manuale della sincronizzazione. Inoltre, semplifica il codice evitando costrutti complessi di sincronizzazione e blocco. Inoltre, la flessibilità delle diverse implementazioni di BlockingQueue consente di adattarsi a vari scenari di utilizzo.
1. Cos'è una BlockingQueue in Java?
2. Quali sono i principali metodi di BlockingQueue che bloccano un thread?
3. A cosa serve BlockingQueue nelle applicazioni multithread?
Grazie per i tuoi commenti!
Chieda ad AI
Chieda ad AI
Chieda pure quello che desidera o provi una delle domande suggerite per iniziare la nostra conversazione
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 e le Sue Implementazioni
Scorri per mostrare il menu
Implementazioni di base di BlockingQueue
Non esamineremo in dettaglio ogni realizzazione, poiché richiederebbe molto tempo e difficilmente saranno tutte necessarie. Verranno illustrati i concetti generali e i costruttori disponibili.
Esempio reale
Immaginare una fabbrica in cui un thread, il produttore, crea dei componenti, mentre un altro thread, il consumatore, li elabora. Il produttore inserisce i componenti in una coda, mentre il consumatore li preleva e li processa dalla coda. Se la coda si svuota, il consumatore attende che il produttore aggiunga altri componenti. Al contrario, se la coda è piena, il produttore attende che il consumatore liberi spazio.
Poco più sotto implementeremo questo compito nel codice.
Differenze rispetto ad altri tipi di collezioni
La BlockingQueue fornisce sincronizzazione automatica, gestendo l'accesso dei thread alla coda senza richiedere sincronizzazione manuale. Supporta inoltre operazioni di blocco per l'aggiunta e il recupero degli elementi, una funzionalità non presente in altre collezioni come ArrayList o LinkedList.
Implementazioni di BlockingQueue
ArrayBlockingQueue: Una coda a dimensione limitata che utilizza un array per memorizzare gli elementi.
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);
Il parametro true abilita una politica di accesso equa fornendo un ordine FIFO per l'accesso dei thread.
LinkedBlockingQueueue: una coda basata su nodi collegati che può essere limitata o illimitata.
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: Coda prioritaria illimitata in cui gli elementi vengono recuperati secondo il loro ordine naturale o come specificato da un comparatore.
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: Una coda ritardata in cui gli elementi possono essere recuperati solo dopo che il loro ritardo è scaduto.
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); } }
Questo codice dimostra l'utilizzo della classe DelayedElement, che implementa l'interfaccia Delayed, e della coda ritardata DelayQueue in Java. La classe DelayedElement definisce un metodo getDelay per calcolare il tempo di ritardo rimanente e un metodo compareTo per confrontare gli oggetti in base al tempo di scadenza del ritardo.
Il metodo main crea due code: queue1, una coda ritardata vuota, e queue2, una coda inizializzata con elementi che hanno rispettivamente un ritardo di 5 e 1 secondo.
Gli elementi in DelayQueueue diventano disponibili per il recupero dopo che è trascorso il tempo di ritardo specificato.
SynchronousQueueue: Una coda senza capacità, in cui ogni operazione di inserimento deve attendere la corrispondente operazione di estrazione e viceversa.
Main.java
12345// Constructor without fair access BlockingQueue<String> queue1 = new SynchronousQueue<>(); // Constructor with fair access BlockingQueue<String> queue2 = new SynchronousQueue<>(true);
I principali metodi di BlockingQueue:
Aggiunta di elementi:
Il metodo void put(E e) inserisce un elemento nella coda, bloccando il thread se la coda è piena. In alternativa, il metodo boolean offer(E e, long timeout, TimeUnit unit) tenta di aggiungere un elemento alla coda, attendendo per il tempo specificato se la coda è piena.
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(); } } }
Questo esempio mostra l'inserimento di due elementi in una BlockingQueue senza bloccare, seguito da un tentativo di aggiungere un terzo elemento con un timeout di 2 secondi utilizzando il metodo offer(), che attenderà se la coda è piena.
Recupero degli elementi:
Il metodo E take() recupera e restituisce un elemento dalla coda, bloccando il thread se la coda è vuota. In alternativa, il metodo E poll(long timeout, TimeUnit unit) tenta di recuperare un elemento dalla coda, attendendo per il tempo specificato se la coda è vuota.
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(); } } }
Questo codice aggiunge due elementi a una BlockingQueue, recupera e rimuove immediatamente il primo elemento, tenta di recuperare il successivo elemento con un timeout di 2 secondi e infine prova a recuperare un elemento da una coda vuota, che restituisce null dopo il timeout.
Verifica e rimozione degli elementi:
Il metodo boolean remove(Object o) rimuove l'elemento specificato dalla coda se presente. Al contrario, il metodo boolean contains(Object o) verifica se l'elemento specificato è presente nella coda senza rimuoverlo.
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(); } } }
Questo codice aggiunge due elementi a una BlockingQueue, verifica la presenza di "Element 1", lo rimuove, controlla nuovamente per confermare la sua rimozione e poi tenta di rimuovere un elemento inesistente.
Interrogazione dello Stato della Coda:
Il metodo int size() restituisce il numero di elementi attualmente presenti nella coda. Per determinare se la coda è vuota, è possibile utilizzare il metodo boolean isEmpty(), che verifica se la coda non contiene elementi. Per le code con capacità fissa, il metodo int remainingCapacity() fornisce il numero di spazi disponibili rimanenti nella coda.
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(); } } }
Questo codice aggiunge elementi a una BlockingQueue, controlla la dimensione attuale, verifica se la coda è vuota e determina la capacità residua, quindi aggiorna questi valori dopo aver riempito completamente la coda.
Realizzazione di un esempio reale in codice
😭 Limitazioni
Una delle principali limitazioni è la prestazione: a causa delle operazioni di locking coinvolte, le prestazioni possono risultare inferiori rispetto alle collezioni non sincronizzate. Inoltre, le risorse possono diventare un problema poiché code di grandi dimensioni richiedono più memoria e tempo CPU per gestire i lock e i processi di sincronizzazione.
💪 Vantaggi
Dal lato positivo, il sistema è sicuro nel multithreading, offrendo una comunicazione sicura tra i thread senza la necessità di una gestione manuale della sincronizzazione. Inoltre, semplifica il codice evitando costrutti complessi di sincronizzazione e blocco. Inoltre, la flessibilità delle diverse implementazioni di BlockingQueue consente di adattarsi a vari scenari di utilizzo.
1. Cos'è una BlockingQueue in Java?
2. Quali sono i principali metodi di BlockingQueue che bloccano un thread?
3. A cosa serve BlockingQueue nelle applicazioni multithread?
Grazie per i tuoi commenti!