Como o CountDownLatch é usado no Multithreading Java?

183

Alguém pode me ajudar a entender o que Java CountDownLatch é e quando usá-lo?

Não tenho uma ideia muito clara de como esse programa funciona. Pelo que entendi, todos os três threads iniciam ao mesmo tempo e cada Thread chama o CountDownLatch após 3000ms. Portanto, a contagem decrescente diminuirá uma a uma. Depois que a trava se torna zero, o programa imprime "Concluído". Talvez a maneira como entendi esteja incorreta.

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class Processor implements Runnable {
    private CountDownLatch latch;

    public Processor(CountDownLatch latch) {
        this.latch = latch;
    }

    public void run() {
        System.out.println("Started.");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        latch.countDown();
    }
}

// ------------------------------------------------ -----

public class App {

    public static void main(String[] args) {

        CountDownLatch latch = new CountDownLatch(3); // coundown from 3 to 0

        ExecutorService executor = Executors.newFixedThreadPool(3); // 3 Threads in pool

        for(int i=0; i < 3; i++) {
            executor.submit(new Processor(latch)); // ref to latch. each time call new Processes latch will count down by 1
        }

        try {
            latch.await();  // wait until latch counted down to 0
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Completed.");
    }

}
amal
fonte
8
Acabei de usar o código de exemplo da sua pergunta para um lote de serviço paralelo do Android e funcionou como um encanto. Muito obrigado!
Roisgoen
Cheguei aqui deste vídeo de 2012, que mostra uma notável semelhança com o exemplo mostrado aqui. Para qualquer pessoa interessada, isso faz parte de uma série de tutoriais sobre multithreading Java de um cara chamado John. Eu gosto do John. Altamente recomendado.
Elia Grady

Respostas:

194

Sim, você entendeu corretamente. CountDownLatchfunciona no princípio da trava, a rosca principal espera até que o portão seja aberto. Um segmento aguarda n encadeamentos, especificados ao criar oCountDownLatch .

Qualquer thread, geralmente o thread principal do aplicativo, que chama CountDownLatch.await() aguardarão até que a contagem chegue a zero ou seja interrompida por outro encadeamento. Todos os outros threads precisam fazer uma contagem regressiva chamandoCountDownLatch.countDown() assim que estiverem concluídos ou prontos.

Assim que a contagem chegar a zero, o encadeamento em espera continuará. Uma das desvantagens / vantagens de CountDownLatchnão ser reutilizável: quando a contagem chega a zero, você não pode usarCountDownLatch .

Editar:

Use CountDownLatchquando um thread (como o thread principal) precisar aguardar a conclusão de um ou mais threads, antes de continuar o processamento.

Um exemplo clássico de uso CountDownLatchem Java é um aplicativo Java principal do lado do servidor que usa arquitetura de serviços, em que vários serviços são fornecidos por vários encadeamentos e o aplicativo não pode iniciar o processamento até que todos os serviços tenham sido iniciados com êxito.

A pergunta da PS OP tem um exemplo bastante direto, então não incluí um.

NikolaB
fonte
1
Obrigado pela resposta. Você poderia me dar um exemplo onde aplicar a trava do CountDown?
Amal
11
um tutorial de como usar o CountDownLatch está aqui howtodoinjava.com/2013/07/18/…
thiagoh
1
@ NikolaB Mas neste exemplo, podemos obter o mesmo resultado usando o método join, não é?
Vikas Verma
3
Eu consideraria a não reutilização uma vantagem: você tem certeza de que ninguém pode redefini-la ou aumentar a contagem.
ataulm
3
Boa explicação. Mas eu discordo um pouco sobre o assunto One thread waits for n number of threads specified while creating CountDownLatch in Java. Se você precisar desse mecanismo, é prudente usar CyclicBarrier. A diferença conceitual fundamental entre estes dois, tal como consta Java concurrency in Practiceé: Latches are for waiting for events; barriers are for waiting for other threads. cyclicBarrier.await()entra em um estado de bloqueio.
Rahul Dev Mishra
43

CountDownLatchin Java é um tipo de sincronizador que permite Thread aguardar um ou mais Threads antes de iniciar o processamento.

CountDownLatchfunciona de acordo com o princípio da trava, a linha aguardará até que o portão seja aberto. Um thread aguarda o nnúmero de threads especificado ao criarCountDownLatch .

por exemplo final CountDownLatch latch = new CountDownLatch(3);

Aqui, ajustamos o contador para 3.

Qualquer encadeamento, geralmente o encadeamento principal do aplicativo, que as chamadas CountDownLatch.await()aguardarão até que a contagem chegue a zero ou seja interrompida por outra Thread. Todos os outros encadeamentos devem fazer a contagem regressiva chamando CountDownLatch.countDown()assim que estiverem concluídos ou prontos para o trabalho. assim que a contagem chegar a zero, a Threadespera começa a correr.

Aqui a contagem é decrementada pelo CountDownLatch.countDown()método

O Threadque chama o await()método aguardará até que a contagem inicial chegue a zero.

Para fazer a contagem zero, outros threads precisam chamar o countDown()método Quando a contagem se tornar zero, o encadeamento que invocou o await()método será retomado (inicie sua execução).

A desvantagem CountDownLatché que não é reutilizável: uma vez que a contagem se torna zero, não é mais utilizável.

Vishal Akkalkote
fonte
usamos o new CountDownLatch(3)como temos 3 threads do newFixedThreadPool definido?
Chaklader Asfak Arefe
não deveria "antes de iniciar o processamento" ser "antes de continuar o processamento"?
Maria Ines Parnisari
@Arefe Sim, é número de threads a atravessar o seu bloco de código
Vishal Akkalkote
23

NikolaB explicou muito bem, no entanto, o exemplo seria útil para entender, então aqui está um exemplo simples ...

 import java.util.concurrent.*;


  public class CountDownLatchExample {

  public static class ProcessThread implements Runnable {

    CountDownLatch latch;
    long workDuration;
    String name;

    public ProcessThread(String name, CountDownLatch latch, long duration){
        this.name= name;
        this.latch = latch;
        this.workDuration = duration;
    }


    public void run() {
        try {
            System.out.println(name +" Processing Something for "+ workDuration/1000 + " Seconds");
            Thread.sleep(workDuration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+ "completed its works");
        //when task finished.. count down the latch count...

        // basically this is same as calling lock object notify(), and object here is latch
        latch.countDown();
    }
}


public static void main(String[] args) {
    // Parent thread creating a latch object
    CountDownLatch latch = new CountDownLatch(3);

    new Thread(new ProcessThread("Worker1",latch, 2000)).start(); // time in millis.. 2 secs
    new Thread(new ProcessThread("Worker2",latch, 6000)).start();//6 secs
    new Thread(new ProcessThread("Worker3",latch, 4000)).start();//4 secs


    System.out.println("waiting for Children processes to complete....");
    try {
        //current thread will get notified if all chidren's are done 
        // and thread will resume from wait() mode.
        latch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("All Process Completed....");

    System.out.println("Parent Thread Resuming work....");



     }
  }
vikashait
fonte
22

É usado quando queremos esperar mais de um thread para concluir sua tarefa. É semelhante à junção de threads.

Onde podemos usar CountDownLatch

Considere um cenário em que temos requisitos em que temos três segmentos "A", "B" e "C" e queremos iniciar o segmento "C" somente quando os segmentos "A" e "B" concluírem ou concluírem parcialmente sua tarefa.

Pode ser aplicado ao cenário de TI do mundo real

Considere um cenário em que o gerente dividiu os módulos entre as equipes de desenvolvimento (A e B) e ele deseja atribuí-lo à equipe de controle de qualidade para teste apenas quando as duas equipes concluírem sua tarefa.

public class Manager {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(2);
        MyDevTeam teamDevA = new MyDevTeam(countDownLatch, "devA");
        MyDevTeam teamDevB = new MyDevTeam(countDownLatch, "devB");
        teamDevA.start();
        teamDevB.start();
        countDownLatch.await();
        MyQATeam qa = new MyQATeam();
        qa.start();
    }   
}

class MyDevTeam extends Thread {   
    CountDownLatch countDownLatch;
    public MyDevTeam (CountDownLatch countDownLatch, String name) {
        super(name);
        this.countDownLatch = countDownLatch;       
    }   
    @Override
    public void run() {
        System.out.println("Task assigned to development team " + Thread.currentThread().getName());
        try {
                Thread.sleep(2000);
        } catch (InterruptedException ex) {
                ex.printStackTrace();
        }
    System.out.println("Task finished by development team Thread.currentThread().getName());
            this.countDownLatch.countDown();
    }
}

class MyQATeam extends Thread {   
    @Override
    public void run() {
        System.out.println("Task assigned to QA team");
        try {
                Thread.sleep(2000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        System.out.println("Task finished by QA team");
    }
}

A saída do código acima será:

Tarefa atribuída à equipe de desenvolvimento devB

Tarefa atribuída à equipe de desenvolvimento devA

Tarefa concluída pela equipe de desenvolvimento devB

Tarefa concluída pela equipe de desenvolvimento devA

Tarefa atribuída à equipe de controle de qualidade

Tarefa concluída pela equipe de controle de qualidade

Aqui, o método waitit () aguarda que o sinalizador countdownlatch se torne 0 e o método countDown () diminui o sinalizador countdownlatch em 1.

Limitação de JOIN: O exemplo acima também pode ser alcançado com JOIN, mas JOIN não pode ser usado em dois cenários:

  1. Quando usamos ExecutorService em vez da classe Thread para criar threads.
  2. Modifique o exemplo acima, onde o Manager deseja transferir o código para a equipe de controle de qualidade assim que o Desenvolvimento concluir sua tarefa de 80%. Isso significa que CountDownLatch nos permite modificar a implementação que pode ser usada para aguardar outro thread para sua execução parcial.
V Jo
fonte
3

CoundDownLatch permite que você faça um thread aguardar até que todos os outros threads sejam concluídos com sua execução.

O pseudo-código pode ser:

// Main thread starts
// Create CountDownLatch for N threads
// Create and start N threads
// Main thread waits on latch
// N threads completes there tasks are returns
// Main thread resume execution
Christophe Roussy
fonte
Você pode querer mover para fora toda sua descrição do bloco de código
Paul Lo
Melhor comentário embora. Gosto desses comentários "diretos" em vez de explicações teóricas.
Renatoaraujoc
2

Um bom exemplo de quando usar algo assim é com o Java Simple Serial Connector, acessando portas seriais. Normalmente, você escreve algo na porta e, de forma assíncrona, em outro encadeamento, o dispositivo responde em um SerialPortEventListener. Normalmente, você deseja fazer uma pausa depois de gravar na porta para aguardar a resposta. O manuseio manual dos bloqueios de encadeamento para esse cenário é extremamente complicado, mas o uso do Countdownlatch é fácil. Antes de pensar que pode fazê-lo de outra maneira, tenha cuidado com as condições de corrida em que nunca pensou!

Pseudo-código:

CountDownLatch latch;
void writeData() { 
   latch = new CountDownLatch(1);
   serialPort.writeBytes(sb.toString().getBytes())
   try {
      latch.await(4, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
   }
}
class SerialPortReader implements SerialPortEventListener {
    public void serialEvent(SerialPortEvent event) {
        if(event.isRXCHAR()){//If data is available
            byte buffer[] = serialPort.readBytes(event.getEventValue());
            latch.countDown();
         }
     }
}

xpusostomos
fonte
2

Se você adicionar alguma depuração após sua chamada para latch.countDown (), isso poderá ajudá-lo a entender melhor seu comportamento.

latch.countDown();
System.out.println("DONE "+this.latch); // Add this debug

A saída mostrará o Count sendo decrementado. Essa 'contagem' é efetivamente o número de tarefas executáveis ​​(objetos do processador) que você iniciou contra as quais countDown () não foi invocado e, portanto, está bloqueado no thread principal em sua chamada para latch.await ().

DONE java.util.concurrent.CountDownLatch@70e69696[Count = 2]
DONE java.util.concurrent.CountDownLatch@70e69696[Count = 1]
DONE java.util.concurrent.CountDownLatch@70e69696[Count = 0]
natmat
fonte
2

Da documentação da Oracle sobre 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.

A CountDownLatché inicializado com uma determinada contagem. Os awaitmétodos bloqueiam até que a contagem atual atinja zero devido a invocações do countDown()método, após o qual todos os threads 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.

Um CountDownLatch é uma ferramenta de sincronização versátil e pode ser usada para diversas finalidades.

Uma CountDownLatchinicializada com uma contagem de um serve como uma trava simples de ligar / desligar, ou gate: todos os threads que chamam aguardam espera no gate até serem abertos por um thread que chama countDown ().

Uma CountDownLatchinicializada para N pode ser usada para fazer um thread aguardar até que N threads concluam alguma ação ou que alguma ação tenha sido concluída N vezes.

public void await()
           throws InterruptedException

Faz com que o encadeamento atual aguarde até que a trava seja contada até zero, a menos que o encadeamento seja interrompido.

Se a contagem atual for zero, esse método retornará imediatamente.

public void countDown()

Decrementa a contagem da trava, liberando todos os segmentos em espera se a contagem chegar a zero.

Se a contagem atual for maior que zero, ela será decrementada. Se a nova contagem for zero, todos os threads em espera serão reativados para fins de planejamento de threads.

Explicação do seu exemplo.

  1. Você definiu count como 3 para a latchvariável

    CountDownLatch latch = new CountDownLatch(3);
  2. Você passou isso compartilhado latchpara o segmento Worker:Processor

  3. Três Runnableinstâncias de Processorforam enviadas paraExecutorService executor
  4. O thread principal ( App) está aguardando a contagem se tornar zero com a instrução abaixo

     latch.await();  
  5. Processor A linha dorme por 3 segundos e depois diminui o valor da contagem com latch.countDown()
  6. A primeira Processinstância alterará a contagem de trava como 2 após sua conclusão devido a latch.countDown().

  7. A segunda Processinstância mudará a contagem de trava como 1 após a conclusão devido a latch.countDown().

  8. A terceira Processinstância alterará a contagem de trava como 0 após sua conclusão devido a latch.countDown().

  9. A contagem zero na trava faz com que a linha principal Appsaiaawait

  10. O programa App imprime esta saída agora: Completed

Ravindra babu
fonte
2

Este exemplo do Java Doc me ajudou a entender claramente os conceitos:

class Driver { // ...
  void main() throws InterruptedException {
    CountDownLatch startSignal = new CountDownLatch(1);
    CountDownLatch doneSignal = new CountDownLatch(N);

    for (int i = 0; i < N; ++i) // create and start threads
      new Thread(new Worker(startSignal, doneSignal)).start();

    doSomethingElse();            // don't let run yet
    startSignal.countDown();      // let all threads proceed
    doSomethingElse();
    doneSignal.await();           // wait for all to finish
  }
}

class Worker implements Runnable {
  private final CountDownLatch startSignal;
  private final CountDownLatch doneSignal;
  Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
     this.startSignal = startSignal;
     this.doneSignal = doneSignal;
  }
  public void run() {
     try {
       startSignal.await();
       doWork();
       doneSignal.countDown();
     } catch (InterruptedException ex) {} // return;
  }

  void doWork() { ... }
}

Interpretação visual:

insira a descrição da imagem aqui

Evidentemente, CountDownLatchpermite que um thread (aqui Driver) aguarde até que vários threads em execução (aqui Worker) sejam concluídos com sua execução.

Saurav Sahu
fonte
1

Conforme mencionado em JavaDoc ( https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html ), CountDownLatch é um auxílio de sincronização, introduzido no Java 5. Aqui a sincronização não é significa restringir o acesso a uma seção crítica. Mas, em vez disso, sequenciar ações de diferentes threads. O tipo de sincronização alcançado através do CountDownLatch é semelhante ao do Join. Suponha que exista um encadeamento "M" que precise aguardar que outros encadeamentos de trabalho "T1", "T2", "T3" concluam suas tarefas. Antes do Java 1.5, da maneira que isso pode ser feito, M executando o código a seguir

    T1.join();
    T2.join();
    T3.join();

O código acima garante que o thread M continue seu trabalho após T1, T2, T3 concluir seu trabalho. T1, T2, T3 podem concluir seu trabalho em qualquer ordem. O mesmo pode ser alcançado através do CountDownLatch, em que T1, T2, T3 e thread M compartilham o mesmo objeto CountDownLatch.
Solicitações "M": countDownLatch.await();
onde "T1", "T2", "T3" não countDownLatch.countdown();

Uma desvantagem do método de junção é que M precisa saber sobre T1, T2, T3. Se houver um novo thread de trabalho T4 adicionado posteriormente, M também precisará estar ciente disso. Isso pode ser evitado com CountDownLatch. Após a implementação, a sequência de ação seria [T1, T2, T3] (a ordem de T1, T2, T3 poderia ser de qualquer maneira) -> [M]

SR Chaitanya
fonte
0
package practice;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch c= new CountDownLatch(3);  // need to decrements the count (3) to zero by calling countDown() method so that main thread will wake up after calling await() method 
        Task t = new Task(c);
        Task t1 = new Task(c);
        Task t2 = new Task(c);
        t.start();
        t1.start();
        t2.start();
        c.await(); // when count becomes zero main thread will wake up 
        System.out.println("This will print after count down latch count become zero");
    }
}

class Task extends Thread{
    CountDownLatch c;

    public Task(CountDownLatch c) {
        this.c = c;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(1000);
            c.countDown();   // each thread decrement the count by one 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
sumit
fonte