Concorrenza Java: blocco conto alla rovescia vs barriera ciclica


160

Stavo leggendo l' API java.util.concurrent e l' ho scoperto

  • CountDownLatch: Un aiuto per la sincronizzazione che consente ad uno o più thread di attendere fino al completamento di una serie di operazioni eseguite in altri thread.
  • CyclicBarrier: Un aiuto per la sincronizzazione che consente a una serie di thread di attendere l'uno per l'altro per raggiungere un punto di barriera comune.

Per me entrambi sembrano uguali, ma sono sicuro che c'è molto di più.

Ad esempio, in CoundownLatch, the countdown value could not be reset, that can happen in the case of CyclicBarrier.

C'è qualche altra differenza tra i due?
Quali sono le use casesaree in cui qualcuno vorrebbe ripristinare il valore del conto alla rovescia?


12
I fermi sono in attesa di eventi; le barriere sono in attesa di altri thread. - Java Concurrency in Practice, B.Goetz et al.
user2418306

Risposte:


137

Una delle principali differenze è che CyclicBarrier esegue un'attività (facoltativa) eseguibile che viene eseguita una volta soddisfatta la condizione di barriera comune.

Inoltre, consente di ottenere il numero di client in attesa alla barriera e il numero richiesto per attivare la barriera. Una volta attivata, la barriera viene ripristinata e può essere riutilizzata.

Per casi d'uso semplici - servizi che iniziano ecc ... un CountdownLatch va bene. Un CyclicBarrier è utile per compiti di coordinamento più complessi. Un esempio di ciò sarebbe il calcolo parallelo - in cui sono coinvolti più sottoattività nel calcolo - un po 'come MapReduce .


6
"Inoltre, consente di ottenere il numero di client in attesa alla barriera e il numero richiesto per attivare la barriera. Una volta attivata, la barriera viene ripristinata e può essere riutilizzata." Mi piace molto questo punto. Un paio di articoli che ho letto suggeriscono che CyclicBarrier è ciclico perché invochi il metodo reset (). Questo è vero, ma ciò che non menzionano spesso è che la barriera viene ripristinata automaticamente non appena viene attivata. Pubblicherò del codice di esempio per illustrare questo.
Kevin Lee,

@Kevin Lee Grazie per "la barriera viene ripristinata automaticamente non appena viene attivata." quindi non è necessario chiamare reset () nel codice.
supernova

134

C'è un'altra differenza.

Quando si utilizza a CyclicBarrier, si presuppone che si specifica il numero di thread in attesa che attivano la barriera. Se si specifica 5, è necessario disporre di almeno 5 thread da chiamare await().

Quando si utilizza a CountDownLatch, si specifica il numero di chiamate a countDown()che comporterà il rilascio di tutti i thread in attesa. Ciò significa che puoi usare a CountDownLatchcon un solo thread.

"Perché dovresti farlo?", Potresti dire. Immagina di utilizzare un'API misteriosa codificata da qualcun altro che esegue callback. Volete che uno dei vostri thread attenda che un certo callback sia stato chiamato più volte. Non hai idea di quali thread verrà richiamato il callback. In questo caso, a CountDownLatchè perfetto, mentre non riesco a pensare ad alcun modo per implementarlo usando un CyclicBarrier(in realtà, posso, ma comporta timeout ... schifo!).

Vorrei solo che CountDownLatchpotesse essere ripristinato!


10
Penso che questa sia la risposta che mostri meglio le differenze teoriche. Il fatto che i latch possano essere rotti chiamando semplicemente più volte un metodo mentre le barriere richiedono un ammontare preciso di thread per attendere ().
flagg19,

43
Giusto - questa è la differenza principale: CountDownLatch -> NumberOfCalls, CyclicBarrier -> NumberOfThreads
Ivan Voroshilin,

1
Sono d'accordo che sarebbe ottimo per CountDownLatchessere ripristinabile - una soluzione alternativa che utilizzo per implementare una notifica di attesa approssimativa è solo quella di riavviare CountDownLatchimmediatamente quando viene inserito il blocco di codice protetto (quando il latch raggiunge lo zero). Questo non è applicabile in tutte le circostanze / ambiti ovviamente, ma ho pensato che valesse la pena notare che è un'opzione in situazioni di riccioli d'oro.
Ephemera,

2
Una delle migliori risposte su questo argomento. Java Concurrency in Practice- dice la stessa cosa: Latches are for waiting for events; barriers are for waiting for other threads.. Un punto primario ed essenziale per comprendere la differenza tra questi due.
Rahul Dev Mishra,

Il documento Java 8 dice "Un CountDownLatch inizializzato su N può essere utilizzato per far attendere un thread fino a quando N thread ha completato alcune azioni o alcune azioni sono state completate N volte." mi sembra: CountDownLatch -> NumberOfCalls O CountDownLatch -> NumberOfThreads
nir

41

Un punto che nessuno ha ancora menzionato è che, in a CyclicBarrier, se un thread ha un problema (timeout, interrotto ...), tutti gli altri che hanno raggiunto await()ottengono un'eccezione. Vedi Javadoc:

CyclicBarrier utilizza un modello di rottura totale o nulla per tentativi di sincronizzazione non riusciti: se un thread lascia prematuramente un punto di barriera a causa di interruzioni, guasti o timeout, anche tutti gli altri thread in attesa in quel punto di barriera lasceranno in modo anomalo tramite BrokenBarrierException (o InterruptedException se anche loro fossero stati interrotti all'incirca nello stesso momento).


22

Penso che JavaDoc abbia spiegato esplicitamente le differenze. Molte persone sanno che CountDownLatch non può essere ripristinato, tuttavia, CyclicBarrier può farlo. Ma questa non è l'unica differenza, oppure CyclicBarrier potrebbe essere rinominato in ResetbleCountDownLatch. Dovremmo dire le differenze dal punto di vista dei loro obiettivi, che sono descritti in JavaDoc

CountDownLatch: un aiuto di sincronizzazione che consente ad uno o più thread di attendere fino al completamento di una serie di operazioni eseguite in altri thread.

CyclicBarrier: un aiuto per la sincronizzazione che consente a una serie di thread di attendere che l'altro raggiunga un punto di barriera comune.

In countDownLatch sono presenti uno o più thread, in attesa del completamento di una serie di altri thread . In questa situazione, ci sono due tipi di thread, un tipo è in attesa, un altro tipo sta facendo qualcosa, dopo aver terminato i propri compiti, potrebbe essere in attesa o appena terminato.

In CyclicBarrier ci sono solo un tipo di thread, si stanno aspettando, sono uguali.


1
"In CyclicBarrier, ci sono solo un tipo di thread" ... Sono uguali nel loro "ruolo di attesa" fino a quando gli altri thread chiamano .await (), ma possono essere "non uguali in quello che fanno". Inoltre, devono essere tutte istanze di thread assolutamente diverse (!) Dello stesso tipo o di tipi diversi, mentre in CountDownLatch lo stesso thread può chiamare countDown () e influenzare il risultato.
Vladimir Nabokov,

Sono d'accordo che CountDownLatch richiede intrinsecamente due ruoli: un client per CountDown e un client per l'attesa. D'altro canto, i client CyclicBarrier possono funzionare perfettamente con il metodo waitit.
isaolmez,

14

La differenza principale è documentata direttamente in Javadocs per CountdownLatch. Vale a dire:

Un CountDownLatch viene inizializzato con un determinato conteggio. I metodi await si bloccano fino a quando il conteggio corrente non raggiunge lo zero a causa di invocazioni del metodo countDown (), dopodiché vengono rilasciati tutti i thread in attesa e le successive invocazioni di wait restituiscono immediatamente. Questo è un fenomeno one-shot - il conteggio non può essere resettato. Se è necessaria una versione che reimposta il conteggio, prendere in considerazione l'utilizzo di CyclicBarrier.

fonte 1.6 Javadoc


4
Se la differenza è semplicemente ripristinabile o meno, CyclicBarrier potrebbe essere meglio chiamato ResetableCountDownLatch, che è più significativo a causa della differenza.
James.Xu

12

Un CountDownLatch viene utilizzato per la sincronizzazione singola. Durante l'utilizzo di CountDownLatch, a qualsiasi thread è consentito chiamare countDown () tutte le volte che lo desiderano. I thread che hanno chiamato waitit () sono bloccati fino a quando il conteggio non raggiunge lo zero a causa delle chiamate a countDown () da parte di altri thread non bloccati. Il javadoc per CountDownLatch afferma:

I metodi await si bloccano fino a quando il conteggio corrente non raggiunge lo zero a causa di invocazioni del metodo countDown (), dopodiché vengono rilasciati tutti i thread in attesa e le successive invocazioni di wait restituiscono immediatamente. ...

Un altro uso tipico sarebbe quello di dividere un problema in N parti, descrivere ogni parte con un Runnable che esegue quella parte e conta alla rovescia sul latch e mettere in coda tutti i Runnable a un Executor. Quando tutte le sottoparti sono complete, il thread di coordinamento sarà in grado di passare in attesa. (Quando i thread devono ripetere il conto alla rovescia in questo modo, utilizzare invece un CyclicBarrier.)

Al contrario, la barriera ciclica viene utilizzata per più punti di sincronizzazione, ad esempio se un insieme di thread esegue un calcolo loop / graduale e deve essere sincronizzato prima di iniziare la successiva iterazione / fase. Come da javadoc per CyclicBarrier :

La barriera è chiamata ciclica perché può essere riutilizzata dopo il rilascio dei thread in attesa.

A differenza di CountDownLatch, ogni chiamata a waitit () appartiene a una fase e può causare il blocco del thread fino a quando tutte le parti appartenenti a quella fase non hanno invocato waitit (). Non esiste alcuna operazione esplicita countDown () supportata da CyclicBarrier.


12

Questa domanda ha già ricevuto una risposta adeguata, ma penso di poter aggiungere un po 'di valore pubblicando del codice.

Per illustrare il comportamento della barriera ciclica, ho creato un codice di esempio. Non appena la barriera viene inclinata, viene automaticamente ripristinata in modo da poterla riutilizzare (quindi è "ciclica"). Quando esegui il programma, osserva che le stampe "Let's play" vengono attivate solo dopo che la barriera è stata inclinata.

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierCycles {

    static CyclicBarrier barrier;

    public static void main(String[] args) throws InterruptedException {
        barrier = new CyclicBarrier(3); 

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);

        System.out.println("Barrier automatically resets.");

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
    }

}


class Worker extends Thread {
    @Override
    public void run() {
        try {
            CyclicBarrierCycles.barrier.await();
            System.out.println("Let's play.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

8

Quando studiavo chiusure e barriere cicliche, mi sono inventato queste metafore. barriere cicliche : immagina che un'azienda abbia una sala riunioni. Per iniziare la riunione, un certo numero di partecipanti alla riunione deve venire alla riunione (per renderla ufficiale). il seguente è il codice di un normale partecipante alla riunione (un dipendente)

class MeetingAtendee implements Runnable {

CyclicBarrier myMeetingQuorumBarrier;

public MeetingAtendee(CyclicBarrier myMileStoneBarrier) {
    this.myMeetingQuorumBarrier = myMileStoneBarrier;
}

@Override
public void run() {
    try {
        System.out.println(Thread.currentThread().getName() + " i joined the meeting ...");
        myMeetingQuorumBarrier.await();
        System.out.println(Thread.currentThread().getName()+" finally meeting stared ...");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        System.out.println("Meeting canceled! every body dance <by chic band!>");
    }
 }
}

l'impiegato si unisce alla riunione, attende che gli altri vengano per iniziare la riunione. inoltre viene abbandonato se la riunione viene annullata :) quindi abbiamo THE BOSS come le dosi non amano aspettare che gli altri si presentino e se perde il suo paziente, annulla la riunione.

class MeetingAtendeeTheBoss implements Runnable {

CyclicBarrier myMeetingQuorumBarrier;

public MeetingAtendeeTheBoss(CyclicBarrier myMileStoneBarrier) {
    this.myMeetingQuorumBarrier = myMileStoneBarrier;
}

@Override
public void run() {
    try {
        System.out.println(Thread.currentThread().getName() + "I am THE BOSS - i joined the meeting ...");
        //boss dose not like to wait too much!! he/she waits for 2 seconds and we END the meeting
        myMeetingQuorumBarrier.await(1,TimeUnit.SECONDS);
        System.out.println(Thread.currentThread().getName()+" finally meeting stared ...");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        System.out.println("what WHO canceled The meeting");
    } catch (TimeoutException e) {
        System.out.println("These employees waste my time!!");
    }
 }
}

In una giornata normale, i dipendenti vengono alla riunione in attesa che altri si presentino e se alcuni partecipanti non arrivano devono aspettare indefinitamente! in una riunione speciale il capo arriva e non gli piace aspettare (5 persone devono iniziare la riunione, ma arriva solo il capo e anche un dipendente entusiasta), quindi annulla la riunione (con rabbia)

CyclicBarrier meetingAtendeeQuorum = new CyclicBarrier(5);
Thread atendeeThread = new Thread(new MeetingAtendee(meetingAtendeeQuorum));
Thread atendeeThreadBoss = new Thread(new MeetingAtendeeTheBoss(meetingAtendeeQuorum));
    atendeeThread.start();
    atendeeThreadBoss.start();

Produzione:

//Thread-1I am THE BOSS - i joined the meeting ...
// Thread-0 i joined the meeting ...
// These employees waste my time!!
// Meeting canceled! every body dance <by chic band!>

Esiste un altro scenario in cui un altro thread esterno (un terremoto) annulla la riunione (metodo di reimpostazione della chiamata). in questo caso tutti i thread in attesa vengono svegliati da un'eccezione.

class NaturalDisasters implements Runnable {

CyclicBarrier someStupidMeetingAtendeeQuorum;

public NaturalDisasters(CyclicBarrier someStupidMeetingAtendeeQuorum) {
    this.someStupidMeetingAtendeeQuorum = someStupidMeetingAtendeeQuorum;
}

void earthQuakeHappening(){
    System.out.println("earth quaking.....");
    someStupidMeetingAtendeeQuorum.reset();
}

@Override
public void run() {
    earthQuakeHappening();
 }
}

l'esecuzione del codice produrrà un output divertente:

// Thread-1I am THE BOSS - i joined the meeting ...
// Thread-0 i joined the meeting ...
// earth quaking.....
// what WHO canceled The meeting
// Meeting canceled! every body dance <by chic band!>

Puoi anche aggiungere un segretario alla sala riunioni, se si tiene una riunione documenterà ogni cosa ma non fa parte della riunione:

class MeetingSecretary implements Runnable {

@Override
public void run() {
        System.out.println("preparing meeting documents");
        System.out.println("taking notes ...");
 }
}

Chiusure : se il capo arrabbiato vuole organizzare un'esibizione per i clienti dell'azienda, ogni cosa deve essere pronta (risorse). forniamo un elenco di cose da fare per ogni lavoratore (Discussione) che dosa il suo lavoro e controlliamo l'elenco di cose da fare (alcuni lavoratori dipingono, altri preparano il sistema audio ...). quando tutti gli elementi nella lista delle cose da fare sono completi (vengono fornite le risorse) possiamo aprire le porte ai clienti.

public class Visitor implements Runnable{

CountDownLatch exhibitonDoorlatch = null;

public Visitor (CountDownLatch latch) {
    exhibitonDoorlatch  = latch;
}

public void run() {
    try {
        exhibitonDoorlatch .await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("customer visiting exebition");
 }
}

E i lavoratori come stanno preparando la mostra:

class Worker implements Runnable {

CountDownLatch myTodoItem = null;

public Worker(CountDownLatch latch) {
    this.myTodoItem = latch;
}

public void run() {
        System.out.println("doing my part of job ...");
        System.out.println("My work is done! remove it from todo list");
        myTodoItem.countDown();
 }
}

    CountDownLatch preperationTodoList = new CountDownLatch(3);

    // exhibition preparation workers  
    Worker      electricalWorker      = new Worker(preperationTodoList);
    Worker      paintingWorker      = new Worker(preperationTodoList);

    // Exhibition Visitors 
    ExhibitionVisitor exhibitionVisitorA = new ExhibitionVisitor(preperationTodoList);
    ExhibitionVisitor exhibitionVisitorB = new ExhibitionVisitor(preperationTodoList);
    ExhibitionVisitor exhibitionVisitorC = new ExhibitionVisitor(preperationTodoList);

    new Thread(electricalWorker).start();
    new Thread(paintingWorker).start();

    new Thread(exhibitionVisitorA).start();
    new Thread(exhibitionVisitorB).start();
    new Thread(exhibitionVisitorC).start();

7

In breve , solo per capire le principali differenze funzionali tra i due:

public class CountDownLatch {
    private Object mutex = new Object();
    private int count;

    public CountDownLatch(int count) {
        this.count = count;
    }

    public void await() throws InterruptedException {
        synchronized (mutex) {
            while (count > 0) {
                mutex.wait();
            }
        }
    }

    public void countDown() {
        synchronized (mutex) {
            if (--count == 0)
                mutex.notifyAll();
        }

    }
}

e

public class CyclicBarrier {
    private Object mutex = new Object();
    private int count;

    public CyclicBarrier(int count) {
        this.count = count;
    }

    public void await() throws InterruptedException {
        synchronized (mutex) {
            count--;
            while(count > 0)
                mutex.wait();
            mutex.notifyAll();
        }
    }
}

tranne, ovviamente, funzionalità come non bloccanti, tempi di attesa, diagnostica e tutto ciò che è stato nei dettagli spiegato nelle risposte sopra.

Le classi di cui sopra sono, tuttavia, pienamente funzionali ed equivalenti, all'interno della funzionalità fornita, ai loro omonimi corrispondenti.

In un'altra nota, CountDownLatchle sottoclassi della classe interna AQS, mentre gli CyclicBarrierusi ReentrantLock(il mio sospetto è che potrebbe essere il contrario o entrambi potrebbero usare AQS o entrambi usare Lock - senza alcuna perdita di efficienza delle prestazioni)


5

Una differenza evidente è che solo N thread possono attendere su un CyclicBarrier di N per essere rilasciato in un ciclo. Ma un numero illimitato di thread può attendere su un CountDownLatch di N. Il decremento del conto alla rovescia può essere eseguito da un thread N volte o N thread una volta ciascuno o combinazioni.


4

Nel caso di CyclicBarrier, non appena TUTTI i thread figlio iniziano a chiamare barrier.await (), il Runnable viene eseguito nella Barriera. Il termine barrier.await in ogni thread figlio richiederà tempi diversi per terminare, e tutti finiscono allo stesso tempo.


4

In CountDownLatch , i thread principali attendono che altri thread completino la loro esecuzione. In CyclicBarrier , i thread di lavoro si aspettano l'un l'altro per completare la loro esecuzione.

Non è possibile riutilizzare la stessa istanza CountDownLatch quando il conteggio arriva a zero e il latch è aperto, d'altra parte CyclicBarrier può essere riutilizzato ripristinando Barrier, una volta che la barriera è stata rotta.


Non è necessario che sia il thread principale. Potrebbe essere qualsiasi thread che crea CountDownLatch e lo condivide con altri thread non principali.
Aniket Thakur,

1

CountDownLatch è un conto alla rovescia di qualsiasi cosa; CyclicBarrier è un conto alla rovescia per il solo thread

supponiamo che ci siano 5 thread di lavoro e uno di shipper e quando i lavoratori producono 100 articoli, lo shipper li spedirà.

Per CountDownLatch, il contatore può trovarsi su lavoratori o articoli

Per CyclicBarrier, il contatore può solo per i lavoratori

Se un lavoratore cade in un sonno infinito, con CountDownLatch sugli articoli, lo spedizioniere può spedire; Tuttavia, con CyclicBarrier, lo spedizioniere non può mai essere chiamato


0

@Kevin Lee e @Jon Ho provato CyclicBarrier con Runnable opzionale. Sembra che funzioni all'inizio e dopo che CyclicBarrier è stato ribaltato. Ecco il codice e l'output

barriera statica CyclicBarrier;

    public static void main(String[] args) throws InterruptedException {
        barrier = new CyclicBarrier(3, new Runnable() {
            @Override
            public void run() {
                System.out.println("I run in the beginning and after the CyclicBarrier is tipped");
            }
        });

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);

        System.out.println("Barrier automatically resets.");

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
    }

Produzione

I run in the beginning and after the CyclicBarrier is tipped
Let's play.
Let's play.
Let's play.
Barrier automatically resets.
I run in the beginning and after the CyclicBarrier is tipped
Let's play.
Let's play.
Let's play.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.