Java CyclicBarrier vs CountDownLatch

1. Introduzione

In questo tutorial, confronteremo CyclicBarrier e CountDownLatch e proveremo a capire le somiglianze e le differenze tra i due.

2. Cosa sono?

Quando si tratta di concorrenza, può essere difficile concettualizzare ciò che ciascuno è destinato a realizzare.

Innanzitutto, sia CountDownLatch che CyclicBarrier vengono utilizzati per la gestione di applicazioni multi-thread .

Ed entrambi hanno lo scopo di esprimere come un dato thread o gruppo di thread dovrebbe aspettare.

2.1. CountDownLatch

Un CountDownLatch è un costrutto in cui un thread attende mentre altri thread eseguono il conto alla rovescia sul latch finché non raggiunge lo zero.

Possiamo pensare a questo come a un piatto in un ristorante che si sta preparando. Indipendentemente dal cuoco che prepara comunque molti degli n elementi, il cameriere deve attendere che tutti gli elementi siano sul piatto. Se un piatto prende n oggetti, qualsiasi cuoco farà il conto alla rovescia sul fermo per ogni oggetto che mette nel piatto.

2.2. CyclicBarrier

Un CyclicBarrier è un costrutto riutilizzabile in cui un gruppo di fili attende insieme fino a quando tutti i fili arrivano . A quel punto, la barriera viene infranta e si può opzionalmente intraprendere un'azione .

Possiamo pensare a questo come a un gruppo di amici. Ogni volta che hanno intenzione di mangiare in un ristorante decidono un punto comune in cui incontrarsi. Si aspettano per l'altro lì, e solo quando tutti arriva possono andare al ristorante a mangiare insieme.

2.3. Ulteriori letture

E per molti più dettagli su ciascuno di questi individualmente, fare riferimento ai nostri tutorial precedenti rispettivamente su CountDownLatch e CyclicBarrier .

3. Attività e thread

Facciamo un'immersione più approfondita in alcune delle differenze semantiche tra queste due classi.

Come indicato nelle definizioni, CyclicBarrier consente a un numero di thread di attendere l'uno sull'altro, mentre CountDownLatch consente a uno o più thread di attendere il completamento di un numero di attività.

In breve, CyclicBarrier mantiene un conteggio dei thread mentre CountDownLatch mantiene un conteggio delle attività .

Nel codice seguente definiamo un CountDownLatch con un conteggio di due. Successivamente, chiamiamo countDown () due volte da un singolo thread:

CountDownLatch countDownLatch = new CountDownLatch(2); Thread t = new Thread(() -> { countDownLatch.countDown(); countDownLatch.countDown(); }); t.start(); countDownLatch.await(); assertEquals(0, countDownLatch.getCount());

Una volta che il latch raggiunge lo zero, la chiamata per attendere ritorna.

Nota che in questo caso, siamo stati in grado di fare in modo che lo stesso thread diminuisca il conteggio due volte.

CyclicBarrier, tuttavia, è diverso su questo punto.

Simile all'esempio precedente, creiamo un CyclicBarrier, di nuovo con un conteggio di due e chiamiamo await () su di esso, questa volta dallo stesso thread:

CyclicBarrier cyclicBarrier = new CyclicBarrier(2); Thread t = new Thread(() -> { try { cyclicBarrier.await(); cyclicBarrier.await(); } catch (InterruptedException | BrokenBarrierException e) { // error handling } }); t.start(); assertEquals(1, cyclicBarrier.getNumberWaiting()); assertFalse(cyclicBarrier.isBroken());

La prima differenza qui è che i fili che stanno aspettando sono essi stessi la barriera.

Secondo, e più importante, il secondo await () è inutile . Un singolo thread non può contare due volte alla rovescia una barriera.

Infatti, poiché t deve aspettare che un altro thread chiami await () - per portare il conteggio a due - la seconda chiamata di t a await () non verrà effettivamente invocata finché la barriera non sarà già superata!

Nel nostro test, la barriera non è stata superata perché abbiamo solo un thread in attesa e non i due thread che sarebbero necessari per far scattare la barriera. Ciò è evidente anche dal metodo cyclicBarrier.isBroken () , che restituisce false .

4. Riusabilità

La seconda differenza più evidente tra queste due classi è la riusabilità. Per elaborare, quando la barriera scatta in CyclicBarrier , il conteggio si ripristina al suo valore originale. CountDownLatch è diverso perché il conteggio non si azzera mai.

Nel codice dato, definiamo un CountDownLatch con count 7 e lo contiamo attraverso 20 chiamate diverse:

CountDownLatch countDownLatch = new CountDownLatch(7); ExecutorService es = Executors.newFixedThreadPool(20); for (int i = 0; i  { long prevValue = countDownLatch.getCount(); countDownLatch.countDown(); if (countDownLatch.getCount() != prevValue) { outputScraper.add("Count Updated"); } }); } es.shutdown(); assertTrue(outputScraper.size() <= 7);

Osserviamo che anche se 20 thread diversi chiamano countDown () , il conteggio non si azzera quando raggiunge lo zero.

Simile all'esempio precedente, definiamo un CyclicBarrier con conteggio 7 e lo attendiamo da 20 thread diversi:

CyclicBarrier cyclicBarrier = new CyclicBarrier(7); ExecutorService es = Executors.newFixedThreadPool(20); for (int i = 0; i  { try { if (cyclicBarrier.getNumberWaiting()  7);

In questo caso, osserviamo che il valore diminuisce ogni volta che viene eseguito un nuovo thread, ripristinando il valore originale, una volta raggiunto lo zero.

5. conclusione

Tutto sommato, CyclicBarrier e CountDownLatchsono entrambi strumenti utili per la sincronizzazione tra più thread. Tuttavia, sono fondamentalmente diversi in termini di funzionalità che forniscono. Considerali attentamente quando stabilisci quale è giusto per il lavoro.

Come al solito, è possibile accedere a tutti gli esempi discussi su Github.