Guida a PriorityBlockingQueue in Java

1. Introduzione

In questo articolo, ci concentreremo sulla classe PriorityBlockingQueue e esamineremo alcuni esempi pratici.

Partendo dal presupposto che sappiamo già cos'è una coda , mostreremo prima come gli elementi in PriorityBlockingQueue sono ordinati per priorità .

Successivamente, mostreremo come questo tipo di coda può essere utilizzato per bloccare un thread.

Infine, mostreremo come utilizzare queste due funzionalità insieme può essere utile durante l'elaborazione dei dati su più thread.

2. Priorità degli elementi

A differenza di una coda standard, non puoi semplicemente aggiungere qualsiasi tipo di elemento a PriorityBlockingQueue. Ci sono due opzioni:

  1. Aggiunta di elementi che implementano Comparable
  2. L'aggiunta di elementi che non implementano Paragonabile , a condizione che si fornisce un comparatore come pure

Utilizzando il Comparator o le implementazioni Comparable per confrontare gli elementi, PriorityBlockingQueue verrà sempre ordinato.

L'obiettivo è implementare la logica di confronto in modo che l'elemento con la priorità più alta sia sempre ordinato per primo . Quindi, quando rimuoviamo un elemento dalla nostra coda, sarà sempre quello con la priorità più alta.

Per cominciare, usiamo la nostra coda in un singolo thread, invece di usarla su più thread. In questo modo, è facile dimostrare come gli elementi vengono ordinati in uno unit test:

PriorityBlockingQueue queue = new PriorityBlockingQueue(); ArrayList polledElements = new ArrayList(); queue.add(1); queue.add(5); queue.add(2); queue.add(3); queue.add(4); queue.drainTo(polledElements); assertThat(polledElements).containsExactly(1, 2, 3, 4, 5);

Come possiamo vedere, nonostante l'aggiunta di elementi alla coda in ordine casuale, verranno ordinati quando inizieremo a interrogarli. Questo perché la classe Integer implementa Comparable, che a sua volta verrà utilizzato per assicurarsi di eliminarli dalla coda in ordine crescente.

Vale anche la pena notare che quando due elementi vengono confrontati e sono uguali, non vi è alcuna garanzia di come verranno ordinati.

3. Utilizzo della coda per bloccare

Se avessimo a che fare con una coda standard, chiameremmo poll () per recuperare gli elementi. Tuttavia, se la coda fosse vuota, una chiamata a poll () restituirebbe null.

Il PriorityBlockingQueue implementa il BlockingQueue interfaccia, che ci dà alcuni metodi extra che ci permettono di blocco quando la rimozione da una coda vuota . Proviamo a utilizzare il metodo take () , che dovrebbe fare esattamente questo:

PriorityBlockingQueue queue = new PriorityBlockingQueue(); new Thread(() -> { System.out.println("Polling..."); try { Integer poll = queue.take(); System.out.println("Polled: " + poll); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); Thread.sleep(TimeUnit.SECONDS.toMillis(5)); System.out.println("Adding to queue"); queue.add(1);

Sebbene l'uso di sleep () sia un modo leggermente fragile per dimostrare le cose, quando eseguiamo questo codice vedremo:

Polling... Adding to queue Polled: 1 

Ciò dimostra che take () è stato bloccato finché non è stato aggiunto un elemento:

  1. Il thread stamperà "Polling" per dimostrare che è stato avviato
  2. Il test si fermerà quindi per circa cinque secondi, per dimostrare che il thread deve aver chiamato take () a questo punto
  3. Aggiungiamo alla coda e dovremmo più o meno immediatamente vedere "Polled: 1" per dimostrare che take () ha restituito un elemento non appena diventa disponibile

Vale anche la pena ricordare che l' interfaccia BlockingQueue ci fornisce anche modi per bloccare quando si aggiungono a code complete.

Tuttavia, un PriorityBlockingQueue è illimitato. Ciò significa che non sarà mai pieno, quindi sarà sempre possibile aggiungere nuovi elementi.

4. Utilizzo combinato di blocco e prioritizzazione

Ora che abbiamo spiegato i due concetti chiave di PriorityBlockingQueue, usiamoli entrambi insieme. Possiamo semplicemente espandere il nostro esempio precedente, ma questa volta aggiungere più elementi alla coda:

Thread thread = new Thread(() -> { System.out.println("Polling..."); while (true) { try { Integer poll = queue.take(); System.out.println("Polled: " + poll); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); Thread.sleep(TimeUnit.SECONDS.toMillis(5)); System.out.println("Adding to queue"); queue.addAll(newArrayList(1, 5, 6, 1, 2, 6, 7)); Thread.sleep(TimeUnit.SECONDS.toMillis(1));

Anche in questo caso, sebbene sia un po 'fragile a causa dell'uso di sleep (), ci mostra comunque un caso d'uso valido. Ora abbiamo una coda che si blocca, in attesa che vengano aggiunti elementi. Stiamo quindi aggiungendo molti elementi contemporaneamente e quindi dimostrando che verranno gestiti in ordine di priorità. L'output sarà simile a questo:

Polling... Adding to queue Polled: 1 Polled: 1 Polled: 2 Polled: 5 Polled: 6 Polled: 6 Polled: 7

5. conclusione

In questa guida, abbiamo dimostrato come possiamo usare una PriorityBlockingQueue per bloccare un thread fino a quando non sono stati aggiunti alcuni elementi e anche che siamo in grado di elaborare quegli elementi in base alla loro priorità.

L'implementazione di questi esempi può essere trovata su GitHub. Questo è un progetto basato su Maven, quindi dovrebbe essere facile da eseguire così com'è.