HEROJOON 블로그(히로블)
Java CountDownLatch를 이용한 Thread 대기 예제 본문
목표
Java에서 제공하는 CountDownLatch를 이해하고 CountDownLatch를 이용하여 Thread 대기 예제 해보기
이해하기
CountDownLatch란?
: CountDownLatch는 Java에서 일련의 스레드 작업이 끝난 후 다음 작업이 진행될 수 있도록 대기 기능을 제공해줍니다.
멀티스레드 환경에서 어떠한 작업들이 수행 된 후 다른 작업이 수행될 수 있도록 하기 위하여 사용됩니다.
- latch의 영어사전 의미: 자물쇠, 걸쇠, 걸쇠를 걸다.
CountDownLatch 설명
- Java 1.5부터 제공된 기능입니다.
- java.util.concurrent 패키지에 포함되어 있습니다.
- 다른 스레드에서 수행 중인 일련의 작업이 완료될 때까지 하나 이상의 스레드를 대기할 수 있도록 기능을 제공합니다.
[대기 방법1] latch의 count가 모두 감소될 때까지 Thread를 계속 대기 시킬 수도 있고 ex) 어떤 조건이 부합할 경우 countDownLatch.countDown()으로 count를 감소시켜 countDownLatch.await() 대기를 해제하는 방법 [대기 방법2] latch의 count가 모두 감소될 때까지 Thread를 대기 시키되 최대 Max 시간만큼만 대기하게 할 수도 있습니다. ex) countDownLatch.await(2, TimeUnit.MINUTES); 처럼 대기 시간을 주어 정해진 시간이 끝나면 count가 모두 감소하지 않아도 await() 대기가 해제되는 방법
CountDownLatch Class 제공기능
● 생성자 (Construct)
- CountDownLatch는 선언 시 전달받은 count로 초기화 됩니다.
- 이 값은 꼭 Thread의 개수 값이 아니어도 되며 작업을 대기 시키기 위한 작업 count나 다음 작업 전에 호출되어야 하는 횟수의 의미로 보시면됩니다.
ex) CountDownLatch countDownLatch = new CountDownLatch(5);
● 주요 메서드 (Methods)
- await()
- CountDownLatch 생성자에 주입한 count가 0이 될 때까지 현재 스레드가 대기하도록 해줍니다.
- await(long timeout, TimeUnit unit)
- CountDownLatch 생성자에 주입한 count가 0이 될 때까지 현재 스레드가 대기하도록 해주되, 최대 정해진 시간까지만 대기하고 해당 시간까지 count가 0이 되지 않으면 대기를 해제합니다.
- countDown()
- CountDownLatch 생성자에 주입한 count(latch의 개수)를 감소시켜줍니다. count가 0이되면 대기 중이던 모든 스레드를 해제합니다.
- getCount()
- 현재 latch의 개수를 반환해줍니다.
● 개발 가이드 (Java 공부 시작하시는 분들은 참고하시면 좋을 것 같아 남겨놓습니다.)
CountDownLatch의 내부 동작 코드 Java Docs를 보시면 예제와 제공 메서드에 대한 설명이 적혀있습니다.
보는 방법은 IntelliJ 기준으로 아래처럼 보시면 됩니다.
CountDownLatch에 [Ctrl] +[마우스 클릭]하여 가이드 문서로 이동합니다.
아래와 같이 이동되며, 상단에 아래처럼 예제가 설명되어 있습니다.
아래에는 Method에 대한 설명도 주석으로 명시되어 있습니다.
해보기 요약
아래 예제 3개를 만들어보았습니다.
- 예제1. 다른 Thread들의 작업이 완료될 때까지 기다렸다가 다음 작업 수행하기
- 예제2. 다른 Thread들의 작업이 완료될 때까지 최대 Max시간만큼 기다렸다가 다음 작업 수행하기
- 예제3. 여러 CountDownLatch를 이용해서 "여러 Thread들이 모두 준비가 완료될 때까지 기다렸다가 Thread들 작업 로직 수행하기"
해보기
● 예제1. 다른 Thread들의 작업이 완료될 때까지 기다렸다가 다음 작업 수행하기
<예제1 체크 포인트>
// await()에 시간을 지정하지 않음
countDownLatch.await();
<예제1 코드>
아래 예제는 [threadCount만큼의 작업 스레드들]이 모두 생성 & 수행된 후 [Finish 로그 출력 작업]이 수행될 수 있도록 시나리오를 구성했습니다.
threadCount만큼의 작업 스레드가 모두 완료되었는지 체크하기 위하여,
CountDownLatch 선언시 Thread 개수 만큼 count를 생성자 주입하여 대기 count를 지정합니다.
CountDownLatch를 이용하여 [작업 스레드들]이 모두 수행 완료될 때까지 [Finish 로그 출력 작업]이 대기할 수 있도록 [Finish 로그 출력 작업] 앞에 await()를 작성해줍니다.
작업 스레드가 수행될 때마다 countDown()을 호출하여 latch의 count를 1씩 감소시켜줍니다.
latch의 count가 0이 되면 대기상태가 풀리면서 await() 다음의 작업이 수행됩니다.
import java.util.concurrent.CountDownLatch;
public class Main {
public static void main(String[] args) throws InterruptedException {
// 코드 실행
executeCode();
}
/**
* CountDownLatch를 이용해서 "다른 Thread들의 작업이 완료될 때까지 기다렸다가 다음 작업 수행하기"
*/
public static void executeCode() throws InterruptedException { // countDownLatch의 await() 사용 시 InterrupedException을 선언해줘야함.
int threadCount = 5; // 생성할 Thread 개수
CountDownLatch countDownLatch = new CountDownLatch(threadCount);
// Java에서 가장 처음 실행되는 메소드는 Main 메소드이므로 가장 처름 표시되는 Thread는 Main Thread 임.
// Main 메소드의 실행도 하나의 Thread 이기 때문.
System.out.println("#### [Start] Thread Name: %s, Thread Id: %s".formatted(
Thread.currentThread().getName(), Thread.currentThread().getId()));
// threadCount 개수 만큼 Thread 생성
for (int i = 0; i < threadCount; i++) {
new Thread(new Worker(countDownLatch)).start();
}
// countDownLatch의 await()를 실행하여 다른 Thread들의 작업이 모두 끝날 때까지 기다림
// countDownLatch의 await()가 대기가 해제되는 시점: countDownLatch의 countDown()이 실행되면서 countdownLatch 생성 시 선언했던 숫자가 0이 될 때
countDownLatch.await();
// 이 부분에 Thread들의 작업이 완료된 후 원하는 작업 수행하기 (저는 로직 대신 아래 로그 출력함.)
// 다른 Thread들의 작업이 모두 끝나면 await() 대기가 해제되면서 아래 로그가 출력됨
System.out.println("#### [Finish] Thread Name: %s, Thread Id: %s".formatted(
Thread.currentThread().getName(), Thread.currentThread().getId()));
}
public static class Worker implements Runnable {
private CountDownLatch countDownLatch;
// Thread 동작 시 countDownLatch의 countDown()을 실행시켜주기 위해 생성자 주입
public Worker(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println("#### [Ongoing] Thread Name: %s, Thread Id: %s".formatted(
Thread.currentThread().getName(), Thread.currentThread().getId()));
// Thread run() 시점에 countDownLatch의 countDown()을 이용하여 countdownLatch 생성 시 선언했던 숫자 감소시킴
countDownLatch.countDown();
}
}
}
/**
[결과]
#### [Start] Thread Name: main, Thread Id: 1
#### [Ongoing] Thread Name: Thread-0, Thread Id: 16
#### [Ongoing] Thread Name: Thread-1, Thread Id: 17
#### [Ongoing] Thread Name: Thread-2, Thread Id: 18
#### [Ongoing] Thread Name: Thread-3, Thread Id: 19
#### [Ongoing] Thread Name: Thread-4, Thread Id: 20
#### [Finish] Thread Name: main, Thread Id: 1
**/
<예제1 코드 실행 결과>
● 예제2. 다른 Thread들의 작업이 완료될 때까지 최대 Max시간만큼 기다렸다가 다음 작업 수행하기
<예제2 체크 포인트>
// await()에 시간을 지정해줌
countDownLatch.await(3, TimeUnit.SECONDS);
<예제2 코드>
아래 예제는 [Finish 로그 출력 작업]이 [다른 스레드 작업들]을 Max시간만큼 대기했다가 수행될 수 있도록 시나리오를 구성했습니다.
작업 스레드들의 수행 완료를 체크하기 위하여,
CountDownLatch 선언시 Thread 개수 만큼 count를 생성자 주입하여 대기 count를 지정합니다.
CountDownLatch를 이용하여 [작업 스레드들]이 모두 수행 완료될 때까지 [Finish 로그 출력 작업]이 대기할 수 있도록 [Finish 로그 출력 작업] 앞에 await()를 작성해줍니다.
하지만 무한 대기가 아닌 정해진 시간만큼 대기할 수 있도록 await(3, TimeUnit.SECONDS)처럼 최대 대기 시간을 입력해줍니다. 예제에서는 3초 대기하도록 입력했습니다.
작업 스레드가 수행될 때마다 countDown()을 호출하여 latch의 count를 1씩 감소시켜줍니다.
여기서 위 await(3, TimeUnit.SECONDS)로 지정한 최대 대기 시간 3초가 지났을 경우 latch 대기가 해제되는지 확인을 위하여 작업 스레드 run() 실행 로직에 Thread.sleep(5000)이라는 5초 대기를 걸어줍니다.
latch의 count가 0이 되기 전 최대 대기 시간이 지남으로써 latch의 대기상태가 풀리고 await() 다음의 작업이 수행됩니다.
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) throws InterruptedException {
// 코드 실행
executeCode();
}
/**
* CountDownLatch를 이용해서 "다른 Thread들의 작업이 완료될 때까지 최대 Max시간만큼 기다렸다가 다음 작업 수행하기"
*/
public static void executeCode() throws InterruptedException { // countDownLatch의 await() 사용 시 InterrupedException을 선언해줘야함.
int threadCount = 5; // 생성할 Thread 개수
CountDownLatch countDownLatch = new CountDownLatch(threadCount);
// Java에서 가장 처음 실행되는 메소드는 Main 메소드이므로 가장 처름 표시되는 Thread는 Main Thread 임.
// Main 메소드의 실행도 하나의 Thread 이기 때문.
System.out.println("#### [Start] Thread Name: %s, Thread Id: %s".formatted(
Thread.currentThread().getName(), Thread.currentThread().getId()));
// threadCount 개수 만큼 Thread 생성
for (int i = 0; i < threadCount; i++) {
new Thread(new Worker(countDownLatch)).start();
}
// countDownLatch의 await()를 실행하여 다른 Thread들의 작업이 모두 끝날 때까지 기다림
// countDownLatch의 await()가 해제되는 시점: countDownLatch의 countDown()이 실행되면서 countdownLatch 생성 시 선언했던 숫자가 0이 될 때
countDownLatch.await(3, TimeUnit.SECONDS); // 3초
// 이 부분에 Thread들의 작업이 완료된 후 원하는 작업 수행하기 (저는 로직 대신 아래 로그 출력함.)
// 다른 Thread들의 작업이 모두 끝나면 await() 대기가 해제되면서 아래 로그가 출력됨
System.out.println("#### [Finish] Thread Name: %s, Thread Id: %s".formatted(
Thread.currentThread().getName(), Thread.currentThread().getId()));
}
public static class Worker implements Runnable {
private CountDownLatch countDownLatch;
// Thread 동작 시 countDownLatch의 countDown()을 실행시켜주기 위해 생성자 주입
public Worker(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
// countDownLatch.await()에 3초를 Max 대기시간으로 주었으므로
// 해당 시간이 넘었을 때 latch가 해제되는지 확인을 위해 5초 동안 기다린 후 로직 실행
Thread.sleep(5000); // 5000의 뜻은 5초 대기 (1000 = 1초)
System.out.println("#### [Ongoing] Thread Name: %s, Thread Id: %s".formatted(
Thread.currentThread().getName(), Thread.currentThread().getId()));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// Thread run() 시점에 countDownLatch의 countDown()을 이용하여 countdownLatch 생성 시 선언했던 숫자 감소시킴
countDownLatch.countDown();
}
}
}
/**
[결과]
#### [Start] Thread Name: main, Thread Id: 1
#### [Finish] Thread Name: main, Thread Id: 1
#### [Ongoing] Thread Name: Thread-2, Thread Id: 18
#### [Ongoing] Thread Name: Thread-3, Thread Id: 19
#### [Ongoing] Thread Name: Thread-4, Thread Id: 20
#### [Ongoing] Thread Name: Thread-0, Thread Id: 16
#### [Ongoing] Thread Name: Thread-1, Thread Id: 17
**/
<예제2 코드 실행 결과>
● 예제3. 여러 CountDownLatch를 이용해서 "다른 Thread들의 작업이 완료될 때까지 기다렸다가 다음 작업 수행하기"
<예제3 체크 포인트>
// CountDownLatch를 여러개 사용
CountDownLatch startSignal = new CountDownLatch(threadCount);
CountDownLatch doneSignal = new CountDownLatch(threadCount);
<예제3 코드>
아래 예제는 다수 CountDownLatch를 사용하여 여러 단계로 대기하며 동작들이 수행되도록 시나리오를 구성했습니다.
- threadCount만큼 Thread가 모두 생성 & 준비 완료될 때까지 다음 작업 대기
- threadCount만큼 생성된 Thread의 로든 작업이 수행 완료될 때까지 다음 작업 대기
- 위 작업들이 모두 완료되면 [Finish 로그 출력 작업] 수행
작업 스레드들의 생성 및 수행 완료를 체크하기 위하여,
CountDownLatch 선언시 Thread 개수 만큼 count를 생성자 주입하여 대기 count를 지정합니다.
// 모든 작업 Thread가 작업할 준비가 되었을 때까지 기다려주는 역할
CountDownLatch startSignal = new CountDownLatch(threadCount);
// 모든 작업 Thread가 모두 수행되었는지 체크하는 역할
CountDownLatch doneSignal = new CountDownLatch(threadCount);
작업 스레드가 생성(준비)될 때마다 countDown()을 호출하여 startSignal latch의 count를 1씩 감소시켜줍니다.
이때, 모든 작업 스레드가 준비된 후 작업 스레드 로직이 수행되도록 startSignal latch를 await()로 대기시켜줍니다.
위 startSignal latch의 대기가 해제되면 다음 작업을 수행해줍니다.
작업 스레드가 수행될 때마다 countDown()을 호출하여 doneSignal latch의 count를 1씩 감소시켜줍니다.
위 작업들이 모두 완료되면 doneSignal latch의 대기도 해제되면서 [Finish 로그 출력 작업] 수행됩니다.
import java.util.concurrent.CountDownLatch;
public class Main {
public static void main(String[] args) throws InterruptedException {
// 코드 실행
executeCode();
}
/**
* 3. 여러 CountDownLatch를 이용해서 "여러 Thread들이 모두 준비가 완료될 때까지 기다렸다가 Thread들 작업 로직 수행하기"
*/
public static void executeCode() throws InterruptedException { // countDownLatch의 await() 사용 시 InterrupedException을 선언해줘야함.
int threadCount = 5; // 생성할 Thread 개수
CountDownLatch startSignal = new CountDownLatch(threadCount); // 모든 작업 Thread가 작업할 준비가 되었을 때까지 기다려주는 역할 (모든 Thread가 생성되었는지 정확하게 확인하기 위해 threadCount를 초기값으로 넣어줌)
CountDownLatch doneSignal = new CountDownLatch(threadCount); // 모든 작업 Thread가 모두 동작되었는지 체크하는 역할
System.out.println("#### [Start] Thread Name: %s, Thread Id: %s".formatted(
Thread.currentThread().getName(), Thread.currentThread().getId()));
for (int i = 0; i < threadCount; ++i) { // create and start threads
new Thread(new Worker(startSignal, doneSignal)).start();
}
doneSignal.await(); // 모든 Thread의 작업이 수행되었다면 await() 대기가 해제되면서 그 다음 로직 실행
// 예제이기 때문에 별도 로직 대신 로그가 출력되게 함.
System.out.println("#### [Finish] Thread Name: %s, Thread Id: %s".formatted(
Thread.currentThread().getName(), Thread.currentThread().getId()));
}
public static class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
/*
startSignal: Thread가 모두 준비되었는지 체크하기 위해 생성자 주입
doneSignal: Thread 동작 시 countDownLatch의 countDown()을 실행시켜주기 위해 생성자 주입
*/
public Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
@Override
public void run() {
System.out.println("#### [Start] Thread Name: %s, Thread Id: %s".formatted(
Thread.currentThread().getName(), Thread.currentThread().getId()));
try {
startSignal.countDown(); // 모든 Thread가 start()되었는지 체크하며 countDown()해줌.
startSignal.await(); // 모든 Thread가 start() 되었다면 await() 대기가 해제되면서 run()로직 실행.
System.out.println("#### [Ongoing] Thread Name: %s, Thread Id: %s".formatted(
Thread.currentThread().getName(), Thread.currentThread().getId()));
doneSignal.countDown();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
/**
[결과]
#### [Start] Thread Name: main, Thread Id: 1
#### [Start] Thread Name: Thread-0, Thread Id: 16
#### [Start] Thread Name: Thread-1, Thread Id: 17
#### [Start] Thread Name: Thread-2, Thread Id: 18
#### [Start] Thread Name: Thread-3, Thread Id: 19
#### [Start] Thread Name: Thread-4, Thread Id: 20
#### [Ongoing] Thread Name: Thread-4, Thread Id: 20
#### [Ongoing] Thread Name: Thread-2, Thread Id: 18
#### [Ongoing] Thread Name: Thread-0, Thread Id: 16
#### [Ongoing] Thread Name: Thread-1, Thread Id: 17
#### [Ongoing] Thread Name: Thread-3, Thread Id: 19
#### [Finish] Thread Name: main, Thread Id: 1
**/
<예제3 코드 실행 결과>
'Backend' 카테고리의 다른 글
Java gRPC API를 사용하여 gRPC 통신 4가지 예제 만들어 보기 (0) | 2024.07.07 |
---|---|
Zookeeper란 (0) | 2024.05.30 |
Spring Boot 3.2.4 배포 시 애플리케이션 동작하지 않을 경우 (0) | 2024.04.07 |
java.lang.UnsupportedOperationException: null 오류 해결 (0) | 2023.01.12 |
업비트 코인 자동매매 RSI 값 구하기 (Java) (4) | 2022.12.02 |