2025. 3. 23. 21:21ㆍBack-End/JAVA
업무 중 스케줄 기반으로 콜을 발신해야 하는 시스템을 설계하게 됐다.
단일 스케줄을 처리하는 로직이 꽤 간단해서 한 개의 쓰레드로만 처리할 수도 있었지만,
여러 건을 동시에 병렬로 처리하고 싶어서 멀티스레드 구조를 도입하기로 했다.
그 과정에서 처음 접하게 된 것이 바로 Runnable 이라는 인터페이스였다.
오늘은 그걸 처음 만났던 경험과, 어떻게 적용했는지를 남겨본다.
Runnable이란?
Java에서 멀티스레드를 사용할 때 “이 스레드가 어떤 작업을 할 건지”를 정의할 수 있어야 한다.
그걸 정의해주는 게 바로 Runnable 인터페이스다.
public interface Runnable {
void run();
}
딱 하나의 메서드 run()만 가지고 있고,
이 안에 “스레드가 해야 할 작업”을 작성하면 된다.
스레드에 Runnable을 넘긴다는 건?
Runnable task = () -> {
System.out.println("별도의 스레드에서 실행됩니다.");
};
new Thread(task).start();
또는
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(task); // 쓰레드풀에 비동기 작업 제출
실전에서의 활용: 콜 발신 워커 설계
나의 경우는 스케줄러가 주기적으로 DB를 조회하고,
콜 발신이 필요한 행을 워커 스레드에 넘겨 처리하는 구조였다.
그래서 다음과 같이 Runnable을 구현한 워커를 만들었다.
public class CallWorker implements Runnable {
private final Schedule schedule;
public CallWorker(Schedule schedule) {
this.schedule = schedule;
}
@Override
public void run() {
for (int i = 0; i < schedule.getMaxRetry(); i++) {
boolean success = call(schedule);
if (success) {
schedule.setResult("SUCC");
schedule.setStatus("DONE");
break;
}
if (i == schedule.getMaxRetry() - 1) {
schedule.setResult("FAIL");
schedule.setStatus("WAIT");
schedule.setTarget("MANAGER");
}
try {
Thread.sleep(schedule.getRetryInterval() * 1000L); // 재시도 대기
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
private boolean call(Schedule schedule) {
// 실제 콜 발신 로직
return false;
}
}
그리고 스케줄러에서는 이렇게 쓰레드풀에 워커를 넘겨 처리했다.
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(new CallWorker(schedule));
사용하면서 느낀 점
- Runnable은 매우 단순하다. 구현도 쉽다.
- 굳이 추상 클래스나 복잡한 객체 없이 딱 하나의 목적에 집중된 로직을 넘기기에 좋았다.
- 여러 Row를 병렬로 처리하되, Row 하나는 워커 하나가 끝까지 책임지는 구조를 만들기 좋았다.
- 스레드풀과 함께 쓰면 병렬성도 확보된다.
+) Callable?
이건 Runnable을 쓰면서 나중에 알게 된 내용인데,
Runnable은 반환값이 없고, 예외도 throws 할 수 없다.
Runnable run = () -> {
// 결과 반환 불가
};
반면 Callable은 결과를 반환할 수 있다.
Callable<String> task = () -> {
return "작업 결과";
};
이럴 땐 Future로 결과를 받을 수도 있고,
예외가 발생하면 try-catch로 처리도 가능하다.
📌 즉,
- 결과값이 필요 없다면 → Runnable
- 결과값이 필요하거나 예외를 던져야 한다면 → Callable
처음엔 인터페이스 하나라고 쉽게 봤는데,
직접 워커 스레드를 설계해보면서 이 Runnable이 어떤 의미인지 제대로 와닿았다.
비동기 시스템에서는 작업 단위 자체를 객체처럼 다뤄야 한다는 점, 그게 정말 중요한 경험이었다.
나처럼 스레드 설계가 처음이라면 Runnable부터 천천히 직접 써보는 걸 추천한다.
'Back-End > JAVA' 카테고리의 다른 글
@RequiredArgsConstructor와 @Builder와의 차이 (0) | 2025.04.22 |
---|---|
final 필드와 @RequiredArgsConstructor는 왜 같이 쓰는가? (0) | 2025.04.22 |
자바 enum, 상수가 전부가 아니었다 (0) | 2025.04.17 |
메서드 참조 :: (0) | 2024.11.22 |
Stream() (0) | 2024.11.22 |