Computer >> 컴퓨터 >  >> 프로그래밍 >> Java

자바 동시성 프로그래밍: CountDownLatch와 CyclicBarrier 차이점 완벽 비교

CountDownLatchCyclicBarrier는 모두 멀티스레드 환경에서 스레드 간 작업을 조율하기 위해 사용되는 대표적인 동기화 도구로, java.util.concurrent 패키지에 포함되어 있습니다.

Java 공식 문서(JavaDoc)에서는 두 클래스를 각각 다음과 같이 정의하고 있습니다.

  • CountDownLatch — 다른 스레드에서 수행 중인 일련의 작업이 완료될 때까지 하나 이상의 스레드가 대기할 수 있도록 지원하는 동기화 보조 클래스입니다.
  • CyclicBarrier — 여러 스레드가 서로 공통된 장벽(barrier) 지점에 도달할 때까지 상호 대기할 수 있도록 지원하는 동기화 보조 클래스입니다.

CountDownLatch와 CyclicBarrier의 주요 차이점

번호구분CyclicBarrierCountDownLatch
1기본 개념여러 스레드가 공통된 장벽 지점에 도달할 때까지 서로를 기다리게 하는 동기화 도구입니다.다른 스레드에서 수행 중인 작업이 완료될 때까지 하나 이상의 스레드가 대기하게 하는 동기화 도구입니다.
2Runnable 지원생성자에서 Runnable(장벽 통과 시 실행될 액션)을 지정할 수 있습니다.Runnable을 받는 생성자는 제공되지 않습니다.
3관리 대상대기하는 스레드 수를 카운트합니다.완료되어야 할 작업(task) 수를 카운트합니다.
4카운트 진행 방식스레드가 실제로 장벽에 도달해야만 진행되므로 외부에서 카운트를 임의로 조작할 수 없습니다.countDown() 메서드 호출을 통해 카운트를 감소(진행)시킬 수 있습니다.
5예외 처리대기 중인 한 스레드가 인터럽트되면, 나머지 대기 스레드들도 모두 BrokenBarrierException이 발생합니다.현재 스레드에만 InterruptedException이 발생하며, 다른 스레드에는 영향을 주지 않습니다.
6재사용 가능 여부장벽이 해제된 후 자동으로 초기화되므로 재사용이 가능합니다(reset() 지원).카운트가 0에 도달하면 더 이상 사용할 수 없는 1회성 도구입니다.

CyclicBarrier 사용 예제

public class Main {
    public static void main(String args[]) throws InterruptedException {
        ExecutorService executors = Executors.newFixedThreadPool(4);
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5);

        executors.submit(new Service1(cyclicBarrier));
        executors.submit(new Service1(cyclicBarrier));
        executors.submit(new Service2(cyclicBarrier));
        executors.submit(new Service2(cyclicBarrier));
        executors.submit(new Service2(cyclicBarrier));

        Thread.sleep(3000);
        System.out.println("Done");
    }
}

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Service1 implements Runnable {
    CyclicBarrier cyclicBarrier;

    public Service1(CyclicBarrier cyclicBarrier) {
        super();
        this.cyclicBarrier = cyclicBarrier;
    }

    @Override
    public void run() {
        System.out.println("Services1 : " + cyclicBarrier.getNumberWaiting());
        while (true) {
            try {
                cyclicBarrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Service2 implements Runnable {
    CyclicBarrier cyclicBarrier;

    public Service2(CyclicBarrier cyclicBarrier) {
        super();
        this.cyclicBarrier = cyclicBarrier;
    }

    @Override
    public void run() {
        System.out.println("Services2 : " + cyclicBarrier.getNumberWaiting());
        while (true) {
            try {
                cyclicBarrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}

CountDownLatch 사용 예제

public class Main {
    public static void main(String args[]) throws InterruptedException {
        ExecutorService executors = Executors.newFixedThreadPool(4);
        CountDownLatch latch = new CountDownLatch(2);

        executors.submit(new Service1(latch));
        executors.submit(new Service2(latch));

        latch.await();
        System.out.println("Done");
    }
}

import java.util.concurrent.CountDownLatch;

public class Service1 implements Runnable {
    CountDownLatch latch;

    public Service1(CountDownLatch latch) {
        super();
        this.latch = latch;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        latch.countDown();
        System.out.println("Services1 : " + latch.getCount());
    }
}

import java.util.concurrent.CountDownLatch;

public class Service2 implements Runnable {
    CountDownLatch latch;

    public Service2(CountDownLatch latch) {
        super();
        this.latch = latch;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        latch.countDown();
        System.out.println("Services2 : " + latch.getCount());
    }
}

정리

CountDownLatch는 특정 작업이나 이벤트가 정해진 횟수만큼 완료될 때까지 대기 스레드를 묶어두는 1회성 래치(latch)이고, CyclicBarrier는 정해진 수의 스레드가 특정 지점에서 서로 만날 때까지 대기한 뒤 함께 진행하며, 사용 후에도 재사용할 수 있는 순환 장벽입니다. 작업 완료 시점까지 기다려야 한다면 CountDownLatch가, 여러 스레드가 특정 시점에 동기화된 후 반복적으로 협업해야 한다면 CyclicBarrier가 더 적합한 선택입니다.