"헐리우드 원칙을 아시나요? '우리에게 전화하지 마세요. 우리가 당신에게 전화할게요(Don't call us, we'll call you).'"
보통은 우리가 함수를 호출하고 결과값을 받습니다(return). 하지만 콜백 패턴에서는 우리가 함수에게 **"일이 끝나면 이 코드를 실행해줘"**라고 함수 자체를 파라미터로 넘깁니다.
하지만 이 콜백이 "기다렸다가 실행되는지(동기)" 아니면 **"나를 놔두고 따로 실행되는지(비동기)"**에 따라 프로그램의 흐름이 완전히 달라집니다.

1. 방식 1: 동기 콜백 (Synchronous Callback) - "기다림의 미학"
자바의 Stream이나 Template Callback 패턴에서 주로 쓰이는 방식입니다. 호출한 함수가 콜백을 모두 실행하고 끝날 때까지 제어권을 돌려주지 않고 기다립니다(Blocking).
특징
- 순차 실행: 코드가 작성된 순서대로 정확하게 실행됩니다.
- 단일 스레드: 특별한 설정이 없으면 메인 스레드에서 쭉 실행됩니다.
코드 예시
// 1. 콜백 인터페이스
interface MyCallback {
void call();
}
// 2. 작업을 수행하는 클래스
class TaskExecutor {
public void execute(MyCallback callback) {
System.out.println("1. 작업 시작");
callback.call(); // 여기서 콜백 실행 (끝날 때까지 다음 줄로 안 넘어감)
System.out.println("3. 작업 종료");
}
}
// 3. 사용 (Client)
public class Main {
public static void main(String[] args) {
TaskExecutor executor = new TaskExecutor();
// 람다로 콜백 전달
executor.execute(() -> System.out.println("2. >> 콜백 로직 수행 중..."));
}
}
실행 결과
1. 작업 시작
2. >> 콜백 로직 수행 중...
3. 작업 종료
(1 -> 2 -> 3 순서가 무조건 보장됨)
장단점
- 장점: 로직의 흐름을 예측하기 쉽고 디버깅이 편합니다.
- 단점: 콜백 로직이 오래 걸리면 전체 시스템이 멈춘 것처럼 보입니다(Blocking).
2. 방식 2: 비동기 콜백 (Asynchronous Callback) - "따로 또 같이"
자바스크립트(Node.js)나 자바의 CompletableFuture, GUI 이벤트 처리에서 주로 쓰입니다. 작업을 별도의 스레드에 던져놓고, 메인 스레드는 기다리지 않고 제 갈 길을 갑니다(Non-blocking).
특징
- 병렬 실행: 메인 로직과 콜백 로직이 동시에(혹은 시차를 두고) 실행됩니다.
- 실행 순서 불확실: 코드는 위에 있지만, 실행은 나중에 될 수 있습니다.
코드 예시
class AsyncTaskExecutor {
public void executeAsync(MyCallback callback) {
System.out.println("1. 작업 요청");
// 새로운 스레드에서 실행 (Non-blocking)
new Thread(() -> {
try { Thread.sleep(2000); } catch (InterruptedException e) {} // 2초 대기
callback.call(); // 2초 뒤에 나중에 실행됨
}).start();
System.out.println("3. 다음 작업 바로 진행");
}
}
// 사용
public class Main {
public static void main(String[] args) {
AsyncTaskExecutor executor = new AsyncTaskExecutor();
executor.executeAsync(() -> System.out.println("2. >> (2초 뒤) 콜백 실행!"));
System.out.println("4. 메인 메서드 끝");
}
}
실행 결과
1. 작업 요청
3. 다음 작업 바로 진행
4. 메인 메서드 끝
2. >> (2초 뒤) 콜백 실행! <-- 나중에 찍힘
(1 -> 3 -> 4 -> 2 순서로 실행됨)
장단점
- 장점: I/O 작업(DB 조회, 파일 읽기)이 오래 걸려도 메인 스레드가 멈추지 않아 성능과 반응성이 좋습니다.
- 단점:
- 콜백 지옥(Callback Hell): 콜백 안에 콜백, 그 안에 콜백... 코드가 들여쓰기 지옥에 빠집니다.
- 디버깅 어려움: 언제 실행될지 예측하기 힘들고 예외 처리가 까다롭습니다.
3. 실무 예시: 자바에서의 진화
1) Java 8 Stream (동기)
List<String> list = Arrays.asList("a", "b", "c");
// forEach 안에 넘기는 람다는 '동기 콜백'입니다.
list.forEach(s -> System.out.println(s));
// 리스트 순회가 끝나야 다음 코드로 넘어갑니다.
2) CompletableFuture (비동기)
자바 8부터는 비동기 콜백 지옥을 해결하기 위해 체이닝 방식을 지원합니다.
CompletableFuture.supplyAsync(() -> {
return "DB 데이터 조회"; // 비동기 실행
}).thenAccept(result -> {
System.out.println(result + " 처리 완료"); // 콜백 (성공 시 실행)
});
요약: 기다릴까, 먼저 갈까?
| 구분 | Synchronous Callback (동기) | Asynchronous Callback (비동기) |
| 제어 흐름 | 호출자(Caller)가 대기함 (Blocking) | 호출자는 즉시 리턴함 (Non-blocking) |
| 실행 스레드 | 주로 Main 스레드 그대로 사용 | 별도의 Worker 스레드 사용 |
| 순서 보장 | 코드 작성 순서 = 실행 순서 | 순서 보장 안 됨 |
| 사용 사례 | 리스트 순회, 전략 패턴, 템플릿 콜백 | AJAX, 파일 I/O, 타이머, 이벤트 리스너 |
결론
"로직의 재사용성을 높이고 싶다(Template Callback)"면 동기 콜백을 사용하세요. 코드가 깔끔해집니다.
하지만 "서버의 응답을 기다리거나, 무거운 작업을 백그라운드에서 처리해야 한다"면 반드시 비동기 콜백을 사용해야 합니다. 단, 비동기 콜백을 쓸 때는 **"콜백 지옥"**을 피하기 위해 CompletableFuture나 RxJava 같은 리액티브 라이브러리의 도움을 받는 것이 정신 건강에 좋습니다.
'프로그래밍 > 디자인패턴' 카테고리의 다른 글
| 사가 패턴(Saga Pattern) 완벽 정리: 코레오그래피(Choreography) vs 오케스트레이션(Orchestration) (0) | 2025.12.07 |
|---|---|
| 페이지네이션 패턴(Pagination Pattern) 완벽 정리 (0) | 2025.12.07 |
| 동시성 제어 패턴: 비관적 락(Pessimistic) vs 낙관적 락(Optimistic) (1) | 2025.12.07 |
| 의존성 주입(DI) 패턴 완벽 정리 (0) | 2025.12.07 |
| 널 객체 패턴(Null Object Pattern) 총정리 (0) | 2025.12.07 |