CountDownLatch는 Java 멀티 스레딩에서 어떻게 사용됩니까?


183

누군가 Java CountDownLatch가 무엇인지 언제 사용 하는지 이해하도록 도와 줄 수 있습니까 ?

이 프로그램의 작동 방식에 대한 명확한 아이디어가 없습니다. 세 개의 스레드가 모두 한 번에 시작되고 각 스레드가 3000ms 후에 CountDownLatch를 호출한다는 것을 이해합니다. 카운트 다운은 하나씩 감소합니다. 래치가 0이되면 프로그램은 "Completed"를 인쇄합니다. 어쩌면 내가 이해 한 방식이 틀릴 수도 있습니다.

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

}


8
방금 귀하의 질문 샘플 코드를 안드로이드 병렬 서비스 배치에 사용했으며 매력처럼 작동했습니다. 정말 고맙습니다!
Roisgoen

여기에서 얻은 동영상 2012에서 보여주는 현저한 유사성 여기에 도시 된 예이다. 관심있는 사람이라면 누구나 John이라는 사람의 Java 멀티 스레딩 자습서 시리즈의 일부입니다. 나는 존을 좋아한다. 추천.
엘리아 그 레이디

답변:


194

예, 당신은 올바르게 이해했습니다. CountDownLatch래치 원리로 작동하면 주 스레드는 게이트가 열릴 때까지 기다립니다. 에 대한 하나 개의 스레드 대기 n 개의 을 만드는 동안 지정된 스레드 CountDownLatch.

CountDownLatch.await()카운트는 0에 도달하거나 다른 스레드에 의해 중단 될 때까지 호출하는 모든 스레드 (일반적으로 응용 프로그램의 기본 스레드)입니다 . 다른 모든 스레드는 CountDownLatch.countDown()완료되거나 준비되면 호출하여 카운트 다운해야합니다 .

카운트가 0에 도달하면 대기 스레드가 계속됩니다. 단점 / 장점 중 하나는 CountDownLatch재사용 할 수 없다는 것 CountDownLatch입니다. 카운트가 0에 도달하면 더 이상 사용할 수 없습니다 .

편집하다:

CountDownLatch하나의 스레드 (예 : 기본 스레드)가 처리를 계속하기 전에 하나 이상의 스레드가 완료 될 때까지 기다려야 할 때 사용하십시오 .

CountDownLatchJava에서 사용하는 전형적인 예 는 서비스 아키텍처를 사용하는 서버 측 핵심 Java 응용 프로그램으로, 여러 스레드에서 여러 서비스를 제공하고 모든 서비스가 성공적으로 시작될 때까지 응용 프로그램이 처리를 시작할 수 없습니다.

PS OP의 질문은 꽤 간단한 예가 있으므로 포함하지 않았습니다.


1
답장을 보내 주셔서 감사합니다. CountDown 래치를 적용 할 위치를 예를 들어 주시겠습니까?
amal

11
CountDownLatch 사용법에 대한 튜토리얼은 여기 howtodoinjava.com/2013/07/18/…
thiagoh

1
@NikolaB 그러나이 예제에서는 join 메소드를 사용하여 동일한 결과를 얻을 수 있습니까?
Vikas Verma

3
재사용 불가능 성을 장점으로 생각합니다. 아무도 다시 설정하거나 카운트를 늘릴 수 없다고 확신합니다.
ataulm

3
좋은 설명입니다. 그러나 나는 요점에 약간 동의하지 않을 것이다 One thread waits for n number of threads specified while creating CountDownLatch in Java. 이러한 메커니즘이 필요한 경우 사용하는 것이 좋습니다 CyclicBarrier. 이 두 가지의 근본적인 개념 차이는 다음과 Java concurrency in Practice같습니다 Latches are for waiting for events; barriers are for waiting for other threads. cyclicBarrier.await()차단 상태가됩니다.
Rahul Dev Mishra

43

CountDownLatchJava에서 처리기 시작하기 전에 Thread 하나 이상의 Threads 를 기다릴 수있는 동기화 프로그램 유형입니다 .

CountDownLatch래치 원리로 작동하면 게이트가 열릴 때까지 스레드가 대기합니다. 하나의 스레드는 n작성하는 동안 지정된 스레드 수를 기다립니다 CountDownLatch.

예 : final CountDownLatch latch = new CountDownLatch(3);

여기서 카운터를 3으로 설정했습니다.

모든 스레드 호출 응용 프로그램의 보통 메인 쓰레드, CountDownLatch.await()카운트 0에 도달하거나 다른 의해 중단 될 때까지 기다립니다 Thread. 다른 모든 스레드는 CountDownLatch.countDown()완료되거나 작업 준비가되면 호출하여 카운트 다운 해야합니다. 카운트가 0에 도달하면 Thread대기가 시작됩니다.

여기서 카운트는 CountDownLatch.countDown()방법에 따라 감소합니다 .

Thread부르는있는 await()방법은 제로 초기 카운트에 도달 할 때까지 기다립니다.

다른 스레드 수를 0으로 만들려면 countDown()메서드 를 호출해야합니다 . 카운트가 0이되면 await()메소드 를 호출 한 스레드 가 재개됩니다 (실행 시작).

단점은 CountDownLatch재사용 할 수 없다는 것입니다. 카운트가 0이되면 더 이상 사용할 수 없습니다.


정의 된 new CountDownLatch(3)스레드가 3 개이므로 를 사용 newFixedThreadPool 합니까?
Chaklader Asfak Arefe

"처리를 시작하기 전에"가 "처리를 계속하기 전에"가 아니어야합니까?
마리아 이네스 파르 니 사리

@Arefe 네, 그것은 당신의 코드 블록을 통과하는 스레드의 수입니다
Vishal Akkalkote

23

NikolaB는 그것을 잘 설명했지만 예제는 이해하는 데 도움이 될 것이므로 간단한 예제가 있습니다.

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



     }
  }

22

하나 이상의 스레드가 작업을 완료하기를 기다릴 때 사용됩니다. 스레드에서 조인하는 것과 비슷합니다.

CountDownLatch를 사용할 수있는 곳

세 개의 스레드 "A", "B"및 "C"가 있고 "A"및 "B"스레드가 작업을 완료하거나 부분적으로 완료 할 때만 스레드 "C"를 시작하려는 시나리오가 있다고 가정합니다.

실제 IT 시나리오에 적용 가능

관리자가 개발 팀 (A와 B)간에 모듈을 나누고 두 팀이 작업을 완료 한 경우에만 테스트를 위해 QA 팀에 모듈을 할당하려는 시나리오를 고려하십시오.

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

위 코드의 출력은 다음과 같습니다.

개발 팀 devB에 할당 된 작업

개발 팀 devA에 할당 된 작업

개발 팀 devB에 의해 완료된 작업

개발 팀 devA에 의해 완료된 작업

품질 보증팀에 할당 된 작업

품질 관리팀에서 작업 완료

여기서 await () 메소드는 countdownlatch 플래그가 0이 될 때까지 대기하고 countDown () 메소드는 countdownlatch 플래그를 1 씩 감소시킵니다.

JOIN의 제한 : 위의 예는 JOIN으로 도 달성 할 수 있지만 JOIN은 두 가지 시나리오에서 사용할 수 없습니다.

  1. Thread 클래스 대신 ExecutorService를 사용하여 스레드를 만드는 경우
  2. 개발이 80 % 작업을 완료하자마자 Manager가 QA 팀에게 코드를 전달하려는 위의 예를 수정하십시오. CountDownLatch를 사용하면 부분 실행을 위해 다른 스레드를 기다리는 데 사용할 수있는 구현을 수정할 수 있습니다.

3

CoundDownLatch를 사용하면 다른 모든 스레드가 실행으로 완료 될 때까지 스레드를 대기시킬 수 있습니다.

의사 코드는 다음과 같습니다.

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

코드 블록에서 모든 설명을 옮기고 싶을 수도 있습니다
Paul Lo

그래도 최고의 의견입니다. 나는 이론적 설명 대신에이 "설명"주석을 좋아한다.
renatoaraujoc

2

이와 같은 것을 사용하는 좋은 예는 Java Simple Serial Connector를 사용하여 직렬 포트에 액세스하는 것입니다. 일반적으로 포트에 무언가를 쓰고 다른 스레드에서는 비동기 적으로 장치가 SerialPortEventListener에서 응답합니다. 일반적으로 포트에 쓴 후 응답을 기다리기 위해 일시 ​​중지하려고합니다. 이 시나리오에서 스레드 잠금을 수동으로 처리하는 것은 매우 까다 롭지 만 Countdownlatch를 사용하는 것은 쉽습니다. 당신이 다른 방법으로 할 수 있다고 생각하기 전에, 당신이 결코 생각하지 않은 경쟁 조건에주의하십시오!

의사 코드 :

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


2

atch.countDown () 호출 후 디버그를 추가하면 동작을 더 잘 이해하는 데 도움이 될 수 있습니다.

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

출력에 카운트가 감소한 것으로 표시됩니다. 이 'count'는 실제로 countDown ()이 호출 되지 않았기 때문에 시작한 Runnable 태스크 (프로세서 오브젝트)의 수 이며, 이에 따라 atch.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]

2

CountDownLatch 에 대한 오라클 문서에서 :

하나 이상의 스레드가 다른 스레드에서 수행중인 작업 세트가 완료 될 때까지 대기 할 수 있도록하는 동기화 지원.

CountDownLatch주어진 카운트로 A 가 초기화됩니다. await현재 카운트가 도달의 호출에 의한 제로까지 차단 방법 countDown()모든 대기중인 스레드가 해제 된 후에있어서, 즉시 AWAIT 복귀 후속 호출. 이것은 일회성 현상이므로 카운트를 재설정 할 수 없습니다.

CountDownLatch는 다목적 동기화 도구이며 다양한 목적으로 사용될 수 있습니다.

A는 CountDownLatch이 호출 스레드 카운트에 의해 개방 될 때까지 대기 게이트 AWAIT 호출 모든 스레드 () : 래치 온 / 오프 간단한 게이트 또는 하나의 카운트가 게재으로 초기화.

CountDownLatchN 초기화는 N 스레드가 어떤 행동을 완료, 또는 어떤 조치가 N 회 완료 될 때까지 하나 개의 스레드가 대기를 만들기 위해 사용할 수 있습니다.

public void await()
           throws InterruptedException

스레드가 중단되지 않는 한 래치가 0으로 카운트 다운 될 때까지 현재 스레드가 대기하도록합니다.

현재 카운트가 0이면이 메소드는 즉시 리턴합니다.

public void countDown()

개수가 0에 도달하면 래치의 개수를 감소시켜 모든 대기 스레드를 해제합니다.

현재 카운트가 0보다 크면 감소합니다. 새 개수가 0이면 모든 대기중인 스레드가 스레드 예약 목적으로 다시 활성화됩니다.

귀하의 예에 대한 설명.

  1. latch변수에 대해 개수를 3으로 설정했습니다

    CountDownLatch latch = new CountDownLatch(3);
  2. 이 공유 latch를 Worker 스레드에 전달했습니다 .Processor

  3. 에 세 개의 Runnable사례 Processor가 제출되었습니다ExecutorService executor
  4. 메인 스레드 ( App)가 아래 명령문으로 카운트가 0이되기를 기다리고 있습니다.

     latch.await();  
  5. Processor 스레드가 3 초 동안 휴면 한 후 다음과 같이 카운트 값이 감소합니다. latch.countDown()
  6. 첫 번째 Process인스턴스는로 인해 완료 후 래치 수를 2로 변경합니다 latch.countDown().

  7. 두 번째 Process인스턴스는로 인해 완료 후 래치 수를 1로 변경합니다 latch.countDown().

  8. 세 번째 Process인스턴스는로 인해 완료 후 래치 카운트를 0으로 변경합니다 latch.countDown().

  9. 래치의 카운트가 0이면 주 스레드 App가 나옵니다.await

  10. 앱 프로그램은 이제이 출력을 인쇄합니다. Completed


2

Java Doc 의이 예제 는 개념을 명확하게 이해하는 데 도움이되었습니다.

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() { ... }
}

시각적 해석 :

여기에 이미지 설명을 입력하십시오

분명히 CountDownLatch하나의 스레드 (here Driver)가 실행중인 스레드 (여기 Worker)가 실행될 때까지 기다릴 수 있습니다.


1

JavaDoc ( https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html ) 에서 언급했듯이 CountDownLatch는 Java 5에 도입 된 동기화 보조 도구입니다. 여기서 동기화는 수행되지 않습니다. 중요한 섹션에 대한 액세스를 제한하는 것을 의미합니다. 그러나 오히려 다른 스레드의 동작을 시퀀싱합니다. CountDownLatch를 통해 달성 된 동기화 유형은 Join과 유사합니다. 작업을 완료하기 위해 다른 작업자 스레드 "T1", "T2", "T3"을 기다려야하는 스레드 "M"이 있다고 가정합니다. Java 1.5 이전에는 다음과 같은 코드를 실행하는 M이 있습니다.

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

위의 코드는 T1, T2, T3이 작업을 완료 한 후 스레드 M이 작업을 재개하도록합니다. T1, T2, T3은 어떤 순서로든 작업을 완료 할 수 있습니다. CountDownLatch를 통해 동일한 작업을 수행 할 수 있습니다. 여기서 T1, T2, T3 및 스레드 M은 동일한 CountDownLatch 객체를 공유합니다.
"M"요청 : countDownLatch.await();
"T1", "T2", "T3"은 countDownLatch.countdown();

결합 방법의 한 가지 단점은 M이 T1, T2, T3에 대해 알아야한다는 것입니다. 나중에 추가 된 새 작업자 스레드 T4가있는 경우 M도이를 인식해야합니다. CountDownLatch를 사용하면이를 피할 수 있습니다. 구현 후 동작 순서는 [T1, T2, T3] (T1, T2, T3의 순서는 어쨌든 가능)-> [M]



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();
        }
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.