CountDownLatch — 카운트다운 동기화 장치

CountDownLatch — 카운트다운 동기화 장치

CountDownLatch다른 스레드에서 수행 중인 일련의 연산이 완료될 때까지 하나 이상의 스레드가 기다리게 하는 동기화 보조 장치예요. 특정 작업들이 모두 끝났을 때 진행을 허용하는 "게이트(gate)"로 자주 쓰여요.

출처: Java API Reference

본문

개념 이해하기

CountDownLatch는 주어진 카운트로 초기화돼요. await 메서드는 countDown() 호출로 현재 카운트가 0이 될 때까지 차단하며, 0이 되면 모든 대기 스레드가 해제되고 이후의 await 호출은 즉시 반환돼요.

public class CountDownLatch
extends Object

이것은 일회성(one-shot) 이에요 — 카운트는 재설정할 수 없어요. 재설정이 필요하면 CyclicBarrier를 고려하세요.

  • 카운트 1: 단순한 on/off 래치 또는 게이트 역할. 모든 await 호출 스레드는 countDown()이 게이트를 열 때까지 기다려요.
  • 카운트 N: 한 스레드가 N개의 스레드가 어떤 액션을 완료하거나, 어떤 액션이 N번 완료될 때까지 기다리게 해요.

유용한 속성: countDown을 호출하는 스레드가 카운트가 0에 도달할 때까지 기다릴 필요 없고, 단지 모든 스레드가 통과할 수 있을 때까지 await를 지나 진행하지 못하게 막는다는 점이에요.

메모리 일관성 효과: 카운트가 0에 도달할 때까지, 한 스레드에서 countDown()을 호출하기 전의 동작은 다른 스레드에서 대응하는 await()의 성공적 반환에 뒤따르는 동작보다 happen-before 관계예요.

활용 예 — 시작 신호 + 완료 신호

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

활용 예 — 문제 분할

문제를 N개로 나누고 각 부분을 실행 후 래치를 감소시키는 Runnable로 기술해 Executor에 제출하면, 모든 부분이 완료됐을 때 조정 스레드가 await를 통과할 수 있어요.

class Driver2 { // ...
    void main() throws InterruptedException {
        CountDownLatch doneSignal = new CountDownLatch(N);
        Executor e = ...;
        for (int i = 0; i < N; ++i)
            e.execute(new WorkerRunnable(doneSignal, i));
        doneSignal.await(); // wait for all to finish
    }
}

생성자

public CountDownLatch(int count) — 주어진 카운트로 초기화된 CountDownLatch를 만들어요.

  • IllegalArgumentExceptioncount가 음수일 때

메서드

public void await() throws InterruptedException — 래치가 0까지 감소될 때까지 현재 스레드를 기다리게 해요. 현재 카운트가 0이면 즉시 반환. 카운트가 0보다 크면 countDown()으로 카운트가 0이 되거나, 다른 스레드가 현재 스레드를 인터럽트할 때까지 대기 상태가 돼요.

  • InterruptedException — 대기 중 인터럽트(인터럽트 상태는 클리어됨)

public boolean await(long timeout, TimeUnit unit) throws InterruptedException — 래치가 0까지 감소되거나, 인터럽트되거나, 지정 대기 시간이 지날 때까지 기다려요. 카운트가 0이 되면 true, 시간이 지나면 false. 시간이 0보다 작거나 같으면 전혀 기다리지 않아요.

  • InterruptedException

public void countDown() — 래치의 카운트를 감소시켜요. 카운트가 0에 도달하면 모든 대기 스레드를 해제해요. 현재 카운트가 0이면 아무 일도 일어나지 않아요.

public long getCount() — 현재 카운트를 반환해요. 주로 디버깅·테스트용.

public String toString() — 이 래치와 상태 "Count =" + 현재 카운트를 식별하는 문자열을 반환해요.

더 알아보기 (Learn more)