Concorrência Java: Trava de contagem regressiva vs barreira cíclica

160

Eu estava lendo a API java.util.concurrent e descobri que

  • CountDownLatch: Um auxiliar de sincronização que permite que um ou mais threads esperem até que um conjunto de operações que estão sendo executadas em outros threads seja concluído.
  • CyclicBarrier: Um auxiliar de sincronização que permite que um conjunto de threads espere um pelo outro atingir um ponto de barreira comum.

Para mim, ambos parecem iguais, mas tenho certeza de que há muito mais.

Por exemplo, em CoundownLatch, the countdown value could not be reset, that can happen in the case of CyclicBarrier.

Existe alguma outra diferença entre os dois? Onde
estão as pessoas use casesque desejam redefinir o valor da contagem regressiva?

sonhador
fonte
12
Travas são para aguardar eventos; barreiras são para aguardar outros threads. - Concorrência Java na Prática, B.Goetz et al.
precisa saber é o seguinte

Respostas:

137

Uma grande diferença é que o CyclicBarrier executa uma tarefa executável (opcional) que é executada quando a condição de barreira comum é atendida.

Ele também permite que você obtenha o número de clientes aguardando na barreira e o número necessário para acionar a barreira. Uma vez acionada, a barreira é redefinida e pode ser usada novamente.

Para casos de uso simples - serviços iniciando etc ... um CountdownLatch é bom. Um CyclicBarrier é útil para tarefas de coordenação mais complexas. Um exemplo disso seria a computação paralela - onde várias subtarefas estão envolvidas na computação - como o MapReduce .

Jon
fonte
6
"Ele também permite que você obtenha o número de clientes aguardando na barreira e o número necessário para acioná-la. Uma vez acionada, a barreira é redefinida e pode ser usada novamente." Eu realmente gosto deste ponto. Alguns artigos que li sugeriram que o CyclicBarrier é cíclico porque você invoca o método reset (). Isso é verdade, mas o que eles não costumam mencionar é que a barreira é redefinida automaticamente assim que é acionada. Vou postar algum código de exemplo para ilustrar isso.
Kevin Lee
@ Kevin Lee Obrigado por "a barreira é redefinida automaticamente assim que é acionada". portanto, não é necessário chamar reset () no código.
supernova
134

Há outra diferença.

Ao usar a CyclicBarrier, pressupõe-se que você especifique o número de threads em espera que acionam a barreira. Se você especificar 5, deverá ter pelo menos 5 threads para chamar await().

Ao usar a CountDownLatch, você especifica o número de chamadas countDown()que resultarão na liberação de todos os threads em espera. Isso significa que você pode usar um CountDownLatchcom apenas um único thread.

"Por que você faria isso?", Você pode dizer. Imagine que você está usando uma API misteriosa codificada por outra pessoa que realiza retornos de chamada. Você deseja que um de seus threads aguarde até que um determinado retorno de chamada seja chamado várias vezes. Você não tem idéia em quais threads o retorno de chamada será chamado. Nesse caso, a CountDownLatché perfeito, enquanto eu não consigo pensar em nenhuma maneira de implementar isso usando a CyclicBarrier(na verdade, eu posso, mas envolve tempos limite ... eca!).

Eu só desejo que CountDownLatchpoderia ser reposto!

Kim
fonte
10
Eu acho que essa é a resposta que melhor mostra as diferenças teóricas. O fato de que as travas podem ser quebradas basta chamar o método várias vezes, enquanto as barreiras precisam de uma quantidade precisa de threads para aguardar ().
flagg19
43
Direito - que é a principal diferença: CountDownLatch -> NumberOfCalls, CyclicBarrier -> NumberOfThreads
Ivan Voroshilin
1
Concordo que seria ótimo CountDownLatchredefinir - uma solução alternativa usada para implementar uma notificação de espera aproximada é apenas atualizar CountDownLatchimediatamente quando o bloco de código protegido for inserido (quando a trava chegar a zero). Isso não é aplicável em todas as circunstâncias / escopos, é claro, mas achei interessante notar que é uma opção em situações de ouro.
Coisas efêmeras
2
Uma das melhores respostas sobre este tópico. Java Concurrency in Practice- diz a mesma coisa: Latches are for waiting for events; barriers are for waiting for other threads.. Um ponto primário e essencial para entender a diferença entre esses dois.
Rahul Dev Mishra
O documento do Java 8 diz que "Um CountDownLatch inicializado em N pode ser usado para fazer um thread aguardar até que N threads concluam alguma ação ou outra ação tenha sido concluída N vezes". parece-me: CountDownLatch -> NumberOfCalls Ou CountDownLatch -> NumberOfThreads
nir 23/02
41

Um ponto que ninguém mencionou ainda é que, em a CyclicBarrier, se um encadeamento tiver um problema (tempo limite, interrompido ...), todos os outros que alcançaram await()obtêm uma exceção. Veja Javadoc:

O CyclicBarrier usa um modelo de quebra tudo ou nada para tentativas falhas de sincronização: se um encadeamento deixar um ponto de barreira prematuramente por causa de interrupção, falha ou tempo limite, todos os outros encadeamentos que esperam nesse ponto de barreira também sairão anormalmente via BrokenBarrierException (ou InterruptedException se eles também foram interrompidos na mesma hora).

Chirlo
fonte
22

Eu acho que o JavaDoc explicou explicitamente as diferenças. A maioria das pessoas sabe que CountDownLatch não pode ser redefinido, no entanto, CyclicBarrier pode. Mas essa não é a única diferença, ou o CyclicBarrier pode ser renomeado para ResetbleCountDownLatch. Devemos dizer as diferenças da perspectiva de seus objetivos, descritos em JavaDoc.

CountDownLatch: um auxiliar de sincronização que permite que um ou mais encadeamentos esperem até que um conjunto de operações sendo executadas em outros encadeamentos seja concluído.

CyclicBarrier: um auxiliar de sincronização que permite que um conjunto de encadeamentos espere um pelo outro atingir um ponto de barreira comum.

Em countDownLatch, há um ou mais encadeamentos que aguardam um conjunto de outros encadeamentos a conclusão de . Nessa situação, existem dois tipos de encadeamentos, um tipo está aguardando, outro está fazendo alguma coisa, após concluir suas tarefas, eles podem estar aguardando ou apenas serem finalizados.

No CyclicBarrier, há apenas um tipo de encadeamentos, eles estão esperando um pelo outro, são iguais.

Justin Civi
fonte
1
"No CyclicBarrier, há apenas um tipo de encadeamentos" ... Eles são iguais em seu "papel de espera" até que outros encadeamentos chamem .await (), mas podem ser "não iguais no que fazem". Além disso, todos eles devem ser absolutamente diferentes instâncias de encadeamento (!) Do mesmo tipo ou de tipos diferentes, enquanto em CountDownLatch o mesmo encadeamento pode chamar countDown () e influenciar o resultado.
Vladimir Nabokov
Concordo que CountDownLatch requer inerentemente duas funções: um cliente para countDown e um cliente para aguardar. Por outro lado, os clientes CyclicBarrier podem operar perfeitamente com o método wait.
isaolmez
14

A principal diferença está documentada nos Javadocs para CountdownLatch. Nomeadamente:

Um CountDownLatch é inicializado com uma determinada contagem. Os métodos de espera são bloqueados até que a contagem atual chegue a zero devido a invocações do método countDown (), após o qual todos os encadeamentos em espera são liberados e quaisquer invocações subseqüentes de aguardam retornam imediatamente. Este é um fenômeno de uma vez - a contagem não pode ser redefinida. Se você precisar de uma versão que redefina a contagem, considere usar um CyclicBarrier.

source 1.6 Javadoc

JohnnyO
fonte
4
Se a diferença for apenas possível de ser redefinida ou não, o CyclicBarrier pode ser melhor denominado ResetableCountDownLatch, o que é mais significativo devido à diferença.
precisa saber é o seguinte
12

Um CountDownLatch é usado para sincronização única. Ao usar um CountDownLatch, qualquer thread pode chamar countDown () quantas vezes quiser. Os encadeamentos que chamaram waitit () são bloqueados até que a contagem chegue a zero devido a chamadas para countDown () por outros encadeamentos desbloqueados. O javadoc para CountDownLatch afirma:

Os métodos de espera são bloqueados até que a contagem atual chegue a zero devido a invocações do método countDown (), após o qual todos os encadeamentos em espera são liberados e quaisquer invocações subseqüentes de aguardam retornam imediatamente. ...

Outro uso típico seria dividir um problema em N partes, descrever cada parte com um Runnable que executa essa parte e faz uma contagem regressiva na trava e enfileira todos os Runnables a um Executor. Quando todas as sub-partes estiverem concluídas, o encadeamento coordenador poderá passar aguardando. (Quando os threads tiverem a contagem regressiva repetidamente dessa maneira, use um CyclicBarrier.)

Por outro lado, a barreira cíclica é usada para vários pontos de sincronização, por exemplo, se um conjunto de threads estiver executando um cálculo em loop / em fases e precisar sincronizar antes de iniciar a próxima iteração / fase. Conforme o javadoc para CyclicBarrier :

A barreira é chamada de cíclica porque pode ser reutilizada após o lançamento dos encadeamentos em espera.

Diferentemente do CountDownLatch, cada chamada para waitit () pertence a alguma fase e pode causar o bloqueio do encadeamento até que todas as partes pertencentes a essa fase invoquem waitit (). Não há operação countDown () explícita suportada pelo CyclicBarrier.

shams
fonte
12

Esta pergunta já foi respondida adequadamente, mas acho que posso agregar um pouco adicionando algum código.

Para ilustrar o comportamento da barreira cíclica, fiz alguns exemplos de código. Assim que a barreira é inclinada, ela é redefinida automaticamente para que possa ser usada novamente (portanto, é "cíclica"). Quando você executa o programa, observe que as impressões "Vamos jogar" são acionadas somente depois que a barreira é derrubada.

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
fonte
8

Quando eu estava estudando sobre Latches e barreiras cíclicas, descobri essas metáforas. cyclicbarriers : imagine que uma empresa tenha uma sala de reuniões. Para iniciar a reunião, um certo número de participantes da reunião precisa comparecer à reunião (para oficializá-la). a seguir está o código de um participante da reunião normal (um funcionário)

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

funcionário ingressa na reunião, aguarda que outros venham para iniciar a reunião. ele também sai se a reunião for cancelada :) então temos o CHEFE como doses não gostam de esperar que outros apareçam e se ele perde o paciente, ele cancela a reunião.

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

Em um dia normal, os funcionários vêm à reunião esperar que outros apareçam e, se alguns participantes não vêm, têm que esperar indefinidamente! em alguma reunião especial, o chefe chega e ele não gosta de esperar (5 pessoas precisam começar a se reunir, mas apenas o chefe vem e também um funcionário entusiasmado); portanto, ele cancela a reunião (com raiva)

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

Resultado:

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

Há outro cenário em que outro segmento externo (um terremoto) cancela a reunião (método de redefinição de chamada). nesse caso, todos os threads em espera são ativados por uma exceção.

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

código em execução resultará em uma saída engraçada:

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

Você também pode adicionar uma secretária à sala de reunião. Se uma reunião for realizada, ela documentará tudo, mas não fará parte da reunião:

class MeetingSecretary implements Runnable {

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

Travas : se o chefe zangado deseja realizar uma exposição para os clientes da empresa, tudo precisa estar pronto (recursos). fornecemos uma lista de tarefas a cada trabalhador (Thread) que administra seu trabalho e verificamos a lista de tarefas (alguns trabalhadores pintam, outros preparam sistema de som ...). quando todos os itens da lista de tarefas estiverem concluídos (os recursos são fornecidos), podemos abrir as portas para os clientes.

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 os trabalhadores como estão preparando a exposição:

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();
Mr.Q
fonte
6

Em poucas palavras , apenas para entender as principais diferenças funcionais entre os dois:

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

exceto, é claro, recursos como bloqueio, espera temporizada, diagnóstico e tudo o que foi detalhado em detalhes nas respostas acima.

As classes acima são, no entanto, totalmente funcionais e equivalentes, dentro da funcionalidade fornecida, aos seus nomes correspondentes.

Em uma nota diferente, CountDownLatchas subclasses da classe interna AQS, enquanto CyclicBarrierusa ReentrantLock(minha suspeita é que poderia ser o contrário ou ambos poderiam usar o AQS ou ambos usar o Lock - sem perda de eficiência de desempenho)

igor.zh
fonte
5

Uma diferença óbvia é que apenas N threads podem aguardar em um CyclicBarrier de N para serem liberados em um ciclo. Mas um número ilimitado de threads pode aguardar em um CountDownLatch de N. O decremento de contagem regressiva pode ser feito por um thread N vezes ou N threads uma vez cada ou combinações.

Pramma
fonte
4

No caso de CyclicBarrier, assim que TODOS os threads filho começam a chamar de barreira.await (), o Runnable é executado na Barreira. A barreira.await em cada encadeamento filho levará um tempo diferente para concluir, e todos serão concluídos ao mesmo tempo.

Brandon
fonte
4

No CountDownLatch , os threads principais aguardam que outros threads concluam sua execução. No CyclicBarrier , os encadeamentos de trabalho esperam um pelo outro para concluir sua execução.

Não é possível reutilizar a mesma instância CountDownLatch quando a contagem chegar a zero e a trava estiver aberta; por outro lado, o CyclicBarrier poderá ser reutilizado redefinindo a barreira, uma vez que a barreira seja quebrada.

V Jo
fonte
Não precisa ser o segmento principal. Pode ser qualquer thread que cria CountDownLatch e o compartilha com outros threads não principais.
Aniket Thakur
1

CountDownLatch é uma contagem regressiva de qualquer coisa; CyclicBarrier é uma contagem regressiva apenas para encadeamento

suponha que existam 5 threads de trabalho e um thread de remetente e quando os trabalhadores produzem 100 itens, o remetente os envia.

Para CountDownLatch, o contador pode estar em trabalhadores ou itens

Para CyclicBarrier, o contador pode apenas em trabalhadores

Se um trabalhador adormecer infinitamente, com CountDownLatch nos itens, o Shipper poderá enviar; No entanto, com CyclicBarrier, o remetente nunca pode ser chamado

yk42b
fonte
0

@ Kevin Lee e @ Jon Tentei o CyclicBarrier com o opcional Runnable. Parece que ele roda no começo e depois que o CyclicBarrier é derrubado. Aqui está o código e a saída

barreira estática 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();
    }

Resultado

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
fonte