web-dev-qa-db-fra.com

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

Je lisais le Java.util.concurrent API , et a constaté que 

  • CountDownLatch: aide à la synchronisation permettant à un ou plusieurs threads d'attendre la fin d'un ensemble d'opérations en cours dans d'autres threads.
  • CyclicBarrier: aide à la synchronisation qui permet à un ensemble de threads d'attendre que l'autre atteigne un point d'arrêt commun. 

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

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 cases où quelqu'un voudrait réinitialiser la valeur du compte à rebours?

141
daydreamer

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

Cela 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 utilisée à nouveau. 

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

117
Jon

Il y a une autre différence.

Lorsque vous utilisez une CyclicBarrier, vous supposez 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 pour appeler await().

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

"Pourquoi feriez-vous cela?", Pourriez-vous dire. Imaginez que vous utilisez une API mystérieuse codée par une autre personne qui effectue des rappels. Vous voulez que l'un de vos threads attende qu'un certain rappel ait été appelé plusieurs fois. Vous n'avez aucune idée des threads sur lesquels le rappel sera appelé. Dans ce cas, une CountDownLatch est parfaite, alors que je ne vois pas comment mettre cela en œuvre en utilisant une CyclicBarrier (en fait, je peux, mais cela implique des délais d'attente ... beurk!).

Je souhaite juste que CountDownLatch puisse être réinitialisé!

113
Kim

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

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

35
Chirlo

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

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

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

Dans countDownLatch, un ou plusieurs threads attendent qu'un ensemble de autres threads se termine. Dans cette situation, il existe deux types de threads: un type est en attente, un autre est en train de faire quelque chose, une fois leurs tâches terminées, ils peuvent être en attente ou simplement terminés. 

Dans CyclicBarrier, il n’existe qu’un seul type de threads, ils s’attendent, ils sont égaux.

19
Justin Civi

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

Un CountDownLatch est initialisé avec un compte donné. Les méthodes en attente bloquent jusqu'à ce que le compte actuel atteigne zéro en raison d'invocations du countDown () méthode, après quoi tout en attente les discussions sont libérées et toute invocations ultérieures de retour en attente immédiatement. Ceci est un one-shot phénomène - le compte ne peut pas être réinitialiser. Si vous avez besoin d'une version qui réinitialise le compte, envisagez d'utiliser un CyclicBarrier.

source 1.6 Javadoc

14
JohnnyO

Cette question a déjà reçu une réponse adéquate, mais je pense pouvoir ajouter un peu de valeur en postant 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();
        }
    }
}
12
Kevin Lee

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

Les méthodes en attente bloquent jusqu'à ce que le nombre actuel atteigne zéro en raison de invocations de la méthode countDown (), après quoi tous les threads en attente sont relâchées et toute invocation ultérieure de retour en attente immédiatement. ...

Une autre utilisation typique serait de diviser un problème en N parties, décrivez chaque partie avec un Runnable qui exécute cette partie et décompte sur le loquet et met tous les Runnables en file d'attente auprès d'un exécuteur . Lorsque toutes les sous-parties sont terminées, le fil de coordination sera capable passer à travers attendre. (Lorsque les threads doivent compter à plusieurs reprises de cette manière, utilisez plutôt un CyclicBarrier.)

Au contraire, la barrière cyclique est utilisée pour plusieurs points de sychronisation, par ex. si un ensemble de threads exécutent un calcul boucle/phasé et ont besoin de se synchroniser avant de commencer la prochaine itération/phase. Selon le javadoc pour CyclicBarrier :

La barrière est appelée cyclique car elle peut être réutilisée après le les threads en attente sont libérés.

Contrairement à CountDownLatch, chaque appel à wait () 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 supportée par CyclicBarrier.

10
shams

Quand j’étudiais les verrous et les barrières cycliques, j’ai imaginé cette métaphores . cyclicbarriers : Imaginez une entreprise ayant une salle de réunion. Afin de commencer 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 à la 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!>");
    }
 }
}

employé rejoint la réunion, attend que les autres viennent pour commencer la réunion. il est également excité si la réunion est annulée :) alors nous avons THE BOSS comment les doses ne veulent pas attendre que les autres se manifestent 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 et attendent que d’autres se présentent. Si certains participants ne viennent pas, ils doivent attendre indéfiniment! lors d’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 un employé enthousiaste), il annule donc 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();

Sortie:

//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 externe (un tremblement de terre) annule la réunion (méthode de réinitialisation d'appel). 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();
 }
}

le code en cours d'exécution donnera 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 à effectuer par chaque travailleur (Fil) et vérifions la liste des tâches à effectuer (certains travailleurs peignent, d'autres préparent des systèmes de sonorisation, etc.). Lorsque tous les éléments de la liste de tâches à faire sont complets (des 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();
7
Mr.Q

En un mot , juste pour comprendre la clé fonctionnelle différences 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 non-blocage, attente minutée, diagnostics et tout ce qui a été expliqué en détail dans les réponses ci-dessus. 

Les classes ci-dessus sont toutefois entièrement fonctionnelles et équivalentes, au sein de la fonctionnalité fournie, à leurs homonymes correspondants.

Sur une note différente, les sous-classes de classe intérieure de CountDownLatchAQS, tandis que CyclicBarrier utilise ReentrantLock (je pense que cela pourrait être autrement ou que les deux pourraient utiliser AQS ou les deux utiliser Lock, sans perte d'efficacité de la performance)

5
igor.zh

Dans le cas de CyclicBarrier, dès que TOUS les threads enfants commencent à appeler barrier.await (), le fichier Runnable est exécuté dans la barrière. La barrière dans chaque thread enfant prendra une longueur différente, et ils finissent tous en même temps. 

4
Brandon

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 of N. Le décompte peut être effectué par un thread N fois ou par N threads une fois chacun ou par combinaisons. 

4
Pramma

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

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

3
V Jo

CountDownLatch est un compte à rebours de n'importe quoi; CyclicBarrier est un compte à rebours pour le fil uniquement

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

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

Pour CyclicBarrier, le compteur ne peut concerner que les travailleurs

Si un ouvrier manque de sommeil, avec CountDownLatch sur les articles, l’expéditeur peut expédier; Cependant, avec CyclicBarrier, Shipper ne peut jamais être appelé

1
yk42b

@ Kevin Lee et @Jon J'ai essayé CyclicBarrier avec Optional Runnable. On dirait qu'il fonctionne 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();
    }

Sortie

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.
0
Hari Rao