Concurrence Java: verrouillage du compte à rebours vs barrière cyclique

160

Je lisais l' API java.util.concurrent et j'ai trouvé que

  • CountDownLatch: Une aide à la synchronisation qui permet à un ou plusieurs threads d'attendre la fin d'un ensemble d'opérations en cours d'exécution dans d'autres threads.
  • CyclicBarrier: Une aide à la synchronisation qui permet à un ensemble de threads d'attendre tous les uns les autres pour atteindre un point de barrière commun.

Pour moi, les deux me semblent égaux, mais je suis sûr qu'il y a beaucoup plus.

Par exemple, dans CoundownLatch, the countdown value could not be reset, that can happen in the case of CyclicBarrier.

Y a-t-il une autre différence entre les deux?
Quels sont les use casesendroits où quelqu'un voudrait réinitialiser la valeur du compte à rebours?

rêveur
la source
12
Les verrous sont destinés à attendre les événements; les barrières sont pour attendre d'autres threads. - La concurrence Java en pratique, B.Goetz et al.
user2418306

Réponses:

137

Une différence majeure est que CyclicBarrier prend une tâche (facultative) qui est exécutée une fois que la condition de barrière commune est remplie.

Il vous permet également d'obtenir le nombre de clients en attente à la barrière et le nombre requis pour déclencher la barrière. Une fois déclenchée, la barrière est réinitialisée et peut être réutilisée.

Pour les cas d'utilisation simples - services démarrant, etc., un CountdownLatch convient. Un CyclicBarrier est utile pour des tâches de coordination plus complexes. Un exemple d'une telle chose serait le calcul parallèle - où plusieurs sous-tâches sont impliquées dans le calcul - un peu comme MapReduce .

Jon
la source
6
"Cela vous permet également d'obtenir le nombre de clients qui attendent à la barrière et le nombre requis pour déclencher la barrière. Une fois déclenchée, la barrière est réinitialisée et peut être réutilisée." J'aime vraiment ce point. Quelques articles que j'ai lus suggèrent que CyclicBarrier est cyclique car vous invoquez la méthode reset (). C'est vrai, mais ce qu'ils ne mentionnent pas souvent, c'est que la barrière est réinitialisée automatiquement dès qu'elle est déclenchée. Je publierai un exemple de code pour illustrer cela.
Kevin Lee
@Kevin Lee Merci pour "la barrière est réinitialisée automatiquement dès qu'elle est déclenchée." donc pas besoin d'appeler reset () dans le code.
supernova
134

Il y a une autre différence.

Lorsque vous utilisez a CyclicBarrier, l'hypothèse est que vous spécifiez le nombre de threads en attente qui déclenchent la barrière. Si vous spécifiez 5, vous devez avoir au moins 5 threads à appeler await().

Lorsque vous utilisez a CountDownLatch, vous spécifiez le nombre d'appels vers countDown()qui entraîneront la libération de tous les threads en attente. Cela signifie que vous pouvez utiliser un CountDownLatchavec un seul thread.

"Pourquoi feriez-vous cela?", Pourriez-vous dire. Imaginez que vous utilisez une mystérieuse API codée par quelqu'un d'autre qui effectue des rappels. Vous voulez que l'un de vos threads attende qu'un certain rappel ait été appelé plusieurs fois. Vous ne savez pas sur quels threads le rappel sera appelé. Dans ce cas, a CountDownLatchest parfait, alors que je ne vois aucun moyen d'implémenter cela en utilisant unCyclicBarrier (en fait, je peux, mais cela implique des délais d'attente ... beurk!).

Je souhaite juste que cela CountDownLatchpuisse être réinitialisé!

Kim
la source
10
Je pense que c'est la réponse qui montre mieux les différences théoriques. Le fait que les verrous peuvent être cassés en appelant simplement plusieurs fois une méthode alors que les barrières ont besoin d'une quantité précise de threads pour wait ().
flagg19
43
Oui, c'est la différence majeure: CountDownLatch -> NumberOfCalls, CyclicBarrier -> NumberOfThreads
Ivan Voroshilin
1
Je suis d'accord que ce serait formidable CountDownLatchd'être réinitialisable - une solution de contournement que j'utilise pour implémenter une notification d'attente approximative consiste simplement à recommencer CountDownLatchimmédiatement lorsque le bloc de code protégé est entré (lorsque le verrou atteint zéro). Ce n'est pas applicable dans toutes les circonstances / portées bien sûr, mais j'ai pensé qu'il vaut la peine de noter que c'est une option dans les situations de boucles d'or.
Ephemera
2
Une des meilleures réponses sur ce sujet. Java Concurrency in Practice- dit la même chose: Latches are for waiting for events; barriers are for waiting for other threads.. Un point primordial et essentiel pour comprendre la différence entre ces deux.
Rahul Dev Mishra
Le document Java 8 dit "Un CountDownLatch initialisé à N peut être utilisé pour faire attendre un thread jusqu'à ce que N threads aient terminé une action ou qu'une action ait été effectuée N fois." me semble: CountDownLatch -> NumberOfCalls Or CountDownLatch -> NumberOfThreads
nir
41

Un point que personne n'a encore évoqué est que, dans un CyclicBarrier, si un thread a un problème (timeout, interrompu ...), tous les autres qui ont atteint await()obtiennent une exception. Voir Javadoc:

Le CyclicBarrier utilise un modèle de rupture tout ou rien pour les tentatives de synchronisation échouées: si un thread quitte un point de barrière prématurément en raison d'une interruption, d'un échec ou d'un délai d'expiration, tous les autres threads en attente à ce point de barrière quitteront également anormalement via BrokenBarrierException (ou InterruptedException s'ils ont également été interrompus à peu près au même moment).

Chirlo
la source
22

Je pense que le JavaDoc a expliqué les différences explicitement. La plupart des gens savent que CountDownLatch ne peut pas être réinitialisé, mais CyclicBarrier le peut. Mais ce n'est pas la seule différence, ou CyclicBarrier pourrait être renommé ResetbleCountDownLatch. Nous devons distinguer les différences du point de vue de leurs objectifs, qui sont décrits dans JavaDoc

CountDownLatch: aide à la synchronisation qui permet à un ou plusieurs threads d'attendre la fin d'un ensemble d'opérations en cours d'exécution dans d'autres threads.

CyclicBarrier: Une aide à la synchronisation qui permet à un ensemble de threads d'attendre tous les uns les autres pour atteindre un point de barrière commun.

Dans countDownLatch, il existe un ou plusieurs threads qui attendent la fin d'un ensemble d' autres threads . Dans cette situation, il existe deux types de threads, un type est en attente, un autre type fait quelque chose, après avoir terminé leurs tâches, ils peuvent être en attente ou simplement terminés.

Dans CyclicBarrier, il n'y a qu'un seul type de threads, ils s'attendent les uns les autres, ils sont égaux.

Justin Civi
la source
1
"Dans CyclicBarrier, il n'y a qu'un seul type de threads" ... Ils sont égaux dans leur "rôle d'attendre" jusqu'à ce que les autres threads appellent .await (), mais ils peuvent être "pas égaux dans ce qu'ils font". En outre, ils doivent tous être des instances de thread absolument différentes (!) Du même type ou de types différents, tandis que dans CountDownLatch, le même thread peut appeler countDown () et influencer le résultat.
Vladimir Nabokov
Je conviens que CountDownLatch nécessite intrinsèquement deux rôles: un client pour countDown et un client pour await. D'autre part, les clients CyclicBarrier peuvent fonctionner correctement avec la méthode await.
isaolmez
14

La principale différence est documentée directement dans les Javadocs pour CountdownLatch. À savoir:

Un CountDownLatch est initialisé avec un nombre donné. Les méthodes d'attente se bloquent jusqu'à ce que le décompte actuel atteigne zéro en raison des appels de la méthode countDown (), après quoi tous les threads en attente sont libérés et toutes les invocations ultérieures de await retournent immédiatement. Il s'agit d'un phénomène ponctuel - le compte ne peut pas être réinitialisé. Si vous avez besoin d'une version qui réinitialise le décompte, envisagez d'utiliser un CyclicBarrier.

source 1.6 Javadoc

JohnnyO
la source
4
Si leur différence est juste peut être réinitialisée ou non, CyclicBarrier pourrait être mieux nommé ResetableCountDownLatch, ce qui est plus significatif en raison de la différence.
James.Xu
12

Un CountDownLatch est utilisé pour une synchronisation ponctuelle. Lors de l'utilisation d'un CountDownLatch, tout thread est autorisé à appeler countDown () autant de fois qu'il le souhaite. Les threads qui ont appelé wait () sont bloqués jusqu'à ce que le nombre atteigne zéro en raison des appels à countDown () par d'autres threads non bloqués. Le javadoc pour CountDownLatch indique:

Les méthodes d'attente se bloquent jusqu'à ce que le nombre actuel atteigne zéro en raison des appels de la méthode countDown (), après quoi tous les threads en attente sont libérés et toutes les invocations ultérieures de wait sont immédiatement renvoyées. ...

Une autre utilisation typique serait de diviser un problème en N parties, de décrire chaque partie avec un Runnable qui exécute cette partie et compte à rebours sur le verrou, et de mettre en file d'attente tous les Runnables vers un Executor. Lorsque toutes les sous-parties sont terminées, le thread de coordination pourra passer à travers wait. (Lorsque les threads doivent effectuer un compte à rebours à plusieurs reprises de cette manière, utilisez plutôt un CyclicBarrier.)

En revanche, la barrière cyclique est utilisée pour plusieurs points de synchronisation, par exemple si un ensemble de threads exécute un calcul en boucle / en phase et doit se synchroniser avant de commencer l'itération / phase suivante. Selon le javadoc pour CyclicBarrier :

La barrière est appelée cyclique car elle peut être réutilisée après la libération des threads en attente.

Contrairement à CountDownLatch, chaque appel à await () appartient à une phase et peut provoquer le blocage du thread jusqu'à ce que toutes les parties appartenant à cette phase aient appelé wait (). Il n'y a pas d'opération countDown () explicite prise en charge par CyclicBarrier.

shams
la source
12

Cette question a déjà reçu une réponse adéquate, mais je pense que je peux ajouter un peu de valeur en publiant du code.

Pour illustrer le comportement de la barrière cyclique, j'ai créé un exemple de code. Dès que la barrière est basculée, elle est automatiquement réinitialisée pour pouvoir être réutilisée (elle est donc "cyclique"). Lorsque vous exécutez le programme, observez que les impressions "Jouons" ne sont déclenchées qu'après le basculement de la barrière.

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();
        }
    }
}
Kevin Lee
la source
8

Lorsque j'étudiais sur les verrous et les barrières cycliques, j'ai trouvé ces métaphores. cyclicbarriers : Imaginez qu'une entreprise dispose d'une salle de réunion. Pour démarrer la réunion, un certain nombre de participants doivent venir à la réunion (pour la rendre officielle). ce qui suit est le code d'un participant normal à une réunion (un employé)

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'employé se joint à la réunion, attend que d'autres viennent pour commencer la réunion. aussi il est sorti si la réunion est annulée :) alors nous avons THE BOSS comment les doses n'aiment pas attendre que les autres se présentent et s'il perd son patient, il annule la réunion.

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!!");
    }
 }
}

Un jour normal, les employés viennent à la réunion en attendant que d'autres se présentent et si certains participants ne viennent pas, ils doivent attendre indéfiniment! dans une réunion spéciale, le patron vient et il n'aime pas attendre. (5 personnes doivent commencer à se rencontrer mais seul le patron vient et aussi un employé enthousiaste) alors il annule la réunion (avec colère)

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

Production:

//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!>

Il existe un autre scénario dans lequel un autre thread extérieur (un tremblement de terre) annule la réunion (méthode de réinitialisation des appels). dans ce cas, tous les threads en attente sont réveillés par une exception.

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'exécution de code entraînera une sortie amusante:

// 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!>

Vous pouvez également ajouter une secrétaire à la salle de réunion, si une réunion a lieu, elle documentera tout mais elle ne fait pas partie de la réunion:

class MeetingSecretary implements Runnable {

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

Loquets : si le patron en colère veut organiser une exposition pour les clients de l'entreprise, tout doit être prêt (ressources). nous fournissons une liste de tâches à chaque travailleur (Thread) dose son travail et nous vérifions la liste de tâches (certains travailleurs font de la peinture, d'autres préparent un système de sonorisation ...). lorsque tous les éléments de la liste de tâches sont terminés (les ressources sont fournies), nous pouvons ouvrir les portes aux clients.

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");
 }
}

Et les ouvriers comment préparent l'exposition:

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();
Monsieur Q
la source
7

En un mot , juste pour comprendre les principales différences fonctionnelles entre les deux:

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();
        }

    }
}

et

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();
        }
    }
}

sauf, bien sûr, des fonctionnalités telles que le non-blocage, l'attente chronométrée, les diagnostics et tout ce qui a été expliqué en détail dans les réponses ci-dessus.

Les classes ci-dessus sont, cependant, entièrement fonctionnelles et équivalentes, dans les fonctionnalités fournies, à leurs homonymes correspondants.

Sur une note différente, CountDownLatchles sous-classes de la classe interne de AQS, while CyclicBarrieruses ReentrantLock(je soupçonne que cela pourrait être l'inverse ou les deux pourraient utiliser AQS ou les deux utiliser Lock - sans aucune perte d'efficacité des performances)

igor.zh
la source
5

Une différence évidente est que seuls N threads peuvent attendre sur un CyclicBarrier de N pour être libérés en un cycle. Mais un nombre illimité de threads peut attendre sur un CountDownLatch de N. La décrémentation du compte à rebours peut être effectuée par un thread N fois ou N threads une fois chacun ou des combinaisons.

Pramma
la source
4

Dans le cas de CyclicBarrier, dès que TOUS les threads enfants commencent à appeler barrière.await (), le Runnable est exécuté dans la barrière. La barrière qui attend dans chaque thread enfant prendra un temps différent pour se terminer, et ils finissent tous en même temps.

Brandon
la source
4

Dans CountDownLatch , les threads principaux attendent que les autres threads terminent leur exécution. Dans CyclicBarrier , les threads de travail attendent les uns les autres pour terminer leur exécution.

Vous ne pouvez pas réutiliser la même instance de CountDownLatch une fois que le nombre atteint zéro et que le verrou est ouvert, par contre, CyclicBarrier peut être réutilisé en réinitialisant Barrier, Une fois que la barrière est cassée.

V Jo
la source
Il n'est pas nécessaire que ce soit le fil conducteur. Il peut s'agir de n'importe quel thread qui crée CountDownLatch et le partage avec d'autres threads non principaux.
Aniket Thakur
1

CountDownLatch est un compte à rebours de tout; CyclicBarrier est un compte à rebours pour thread uniquement

Supposons qu'il y ait 5 fils de travail et un fil d'expéditeur, et lorsque les travailleurs produisent 100 articles, l'expéditeur les expédiera.

Pour CountDownLatch, le compteur peut être sur des travailleurs ou des éléments

Pour CyclicBarrier, le compteur ne peut que sur les travailleurs

Si un ouvrier dort infiniment, avec CountDownLatch sur les articles, l'expéditeur peut expédier; Cependant, avec CyclicBarrier, Shipper ne peut jamais être appelé

yk42b
la source
0

@Kevin Lee et @Jon J'ai essayé CyclicBarrier avec Optional Runnable. On dirait qu'il s'exécute au début et après le basculement de CyclicBarrier. Voici le code et la sortie

barrière statique 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();
    }

Production

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.
Hari Rao
la source