4주차 Unit 2.1 — Sync vs Async (작업 순서 관점)

Psj·2026년 5월 20일

F-lab

목록 보기
124/239

Unit 2.1 — Sync vs Async (작업 순서 관점)

F-LAB JAVA · 4주차 · Phase 2 · Sync/Async × Blocking/Non-Blocking 4분면
🚀 Phase 2 시작 — 4분면 매트릭스 진입


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • 동기 (Synchronous) 의 정의와 핵심은?
  • 비동기 (Asynchronous) 의 정의와 핵심은?
  • Sync vs Async 의 판단 기준 (작업 순서) 은?
  • "이전 작업의 완료 여부를 확인하는가?" 의 의미는?
  • "DB 쿼리를 비동기로 처리" 의 정확한 의미는?
  • 비동기가 항상 빠른가? (처리 시간 vs 처리량) 의 답은?
  • 콜백 (Callback) 으로 비동기 결과를 받는 방법은?
  • 동기/비동기 vs 블로킹/논블로킹 의 축 차이는?
  • 자바의 동기/비동기 도구 는?

🎯 핵심 한 문장

동기 (Sync) 와 비동기 (Async) 는 "작업의 순서와 완료 통보" 를 다루는 축으로, 호출자가 이전 작업의 완료 여부를 확인하고 다음으로 넘어가는가 가 판단 기준이다.
동기 (Synchronous) 는 작업 A 의 완료를 확인한 후 B 를 실행 (순서 보장), 비동기 (Asynchronous) 는 A 의 완료를 기다리지 않고 B 를 실행 (순서 무관, 완료는 나중에 통보/콜백).
핵심 질문: "다음 작업을 위해 이전 작업의 완료 여부를 확인하는가?" — Yes 면 동기, No 면 비동기.
비동기가 항상 빠른 것은 아니다 — 단일 작업의 처리 시간 (latency) 은 같거나 오히려 느릴 수 있고, 시스템 전체의 처리량 (throughput) 이 향상되는 것.
비동기 결과는 콜백 (callback) 또는 Future/Promise 로 받으며, 이 축은 블로킹/논블로킹 (제어권 축) 과는 다른 차원이다 (다음 Unit).

비유 — 식당 주문

동기 (Sync):
  카운터에서 주문하고 음식 나올 때까지 기다림
  - 주문 (작업 A)
  - 완료 확인 (음식 나옴) 후
  - 자리로 (작업 B)
  - 순서 보장

비동기 (Async):
  진동벨 받고 자리로
  - 주문 (작업 A)
  - 완료 안 기다리고 자리로 (작업 B)
  - 진동벨 울리면 (콜백) 가지러
  - 순서 무관

핵심:
  - 동기: 완료 확인 후 다음
  - 비동기: 완료 안 기다리고 다음, 나중에 통보

→ 동기 = 완료 확인 후 진행, 비동기 = 완료 안 기다림 + 나중 통보.


🧭 9개 섹션 로드맵

1. 동기 (Synchronous) 의 정의
2. 비동기 (Asynchronous) 의 정의
3. 판단 기준 — 작업 순서
4. "완료 여부를 확인하는가?"
5. DB 쿼리 비동기의 의미
6. 비동기가 항상 빠른가
7. 콜백으로 결과 받기
8. Sync/Async vs Blocking/Non-Blocking
9. 면접 + 자기 점검

1️⃣ 동기 (Synchronous) 의 정의

1.1 동기의 정의

동기 (Synchronous):

  작업 A 의 완료를 확인한 후
  다음 작업 B 를 실행하는 방식.

핵심:
  - 순서 보장
  - 완료 확인
  - 결과를 직접 받음

1.2 동기의 흐름

동기 처리 흐름:

호출자:
  1. 작업 A 호출
  2. A 완료 대기 (또는 확인)
  3. A 결과 받음
  4. 작업 B 시작
  5. ...

  순차적
  A → B → C

1.3 코드 예시

// 동기 처리
public void syncExample() {
    String data = fetchData();        // 1. 완료까지 대기
    String processed = process(data); // 2. data 받은 후 처리
    save(processed);                  // 3. 순차
    
    // 각 단계가 완료된 후 다음
    // 순서 보장
}

private String fetchData() {
    // 데이터 조회 (완료까지)
    return "data";
}

1.4 동기의 특징

동기의 특징:

장점:
  - 단순, 직관적
  - 순서 보장
  - 디버깅 쉬움
  - 결과 직접 받음

단점:
  - 대기 시간 (완료까지)
  - 자원 비효율 (대기 중 놀음)
  - 처리량 한계

1.5 동기 = 블로킹?

주의 — 동기 ≠ 블로킹:

동기 (Sync):
  - 작업 순서 축
  - 완료 확인

블로킹 (Blocking):
  - 제어권 축
  - 대기 여부

흔히 혼동하지만 다른 개념.
다음 Unit (2.2) 에서 정밀.

대부분의 동기는 블로킹이지만
동기 + 논블로킹도 가능 (polling)

1.6 ILIC 의 동기 처리

@Service
public class ShipmentSyncService {
    
    // 동기 처리 — 순차
    public ShipmentResult process(Long id) {
        // 1. 조회 (완료까지)
        Shipment shipment = repository.findById(id);
        
        // 2. 운임 계산 (조회 완료 후)
        BigDecimal freight = calculateFreight(shipment);
        
        // 3. 검증 (계산 완료 후)
        validate(shipment, freight);
        
        // 4. 저장 (검증 완료 후)
        repository.save(shipment);
        
        // 순차적, 각 단계 완료 후 다음
        return new ShipmentResult(shipment, freight);
    }
}

1.7 자기 점검 답변

동기의 정의는?

:
1. 정의:

  • 작업 A 완료 확인 후 B
  • 순서 보장
  1. 흐름:

    • A → 대기 → 결과 → B
    • 순차
  2. 특징:

    • 단순, 직관적
    • 대기 시간
    • 결과 직접
  3. 주의:

    • 동기 ≠ 블로킹
    • 작업 순서 축

2️⃣ 비동기 (Asynchronous) 의 정의

2.1 비동기의 정의

비동기 (Asynchronous):

  작업 A 의 완료를 기다리지 않고
  다음 작업 B 를 실행하는 방식.

핵심:
  - 순서 무관
  - 완료 안 기다림
  - 완료는 나중에 통보 (콜백/Future)

2.2 비동기의 흐름

비동기 처리 흐름:

호출자:
  1. 작업 A 호출 (시작만)
  2. A 완료 안 기다림
  3. 작업 B 시작 (A 와 무관)
  ...
  N. A 완료 시 → 콜백/통보

  비순차적
  A 시작 → B 시작 → ... → A 완료 통보

2.3 코드 예시

// 비동기 처리
public void asyncExample() {
    // 1. 작업 시작 (완료 안 기다림)
    CompletableFuture<String> future = fetchDataAsync();
    
    // 2. 다른 작업 (A 완료 무관)
    doOtherWork();
    
    // 3. 완료 시 콜백
    future.thenAccept(data -> {
        process(data);   // A 완료 후 자동 실행
    });
    
    // 호출자는 A 완료 안 기다리고 진행
}

private CompletableFuture<String> fetchDataAsync() {
    return CompletableFuture.supplyAsync(() -> {
        // 백그라운드에서 조회
        return "data";
    });
}

2.4 비동기의 특징

비동기의 특징:

장점:
  - 대기 시간 활용
  - 자원 효율
  - 처리량 ↑
  - 응답성 ↑

단점:
  - 복잡 (콜백 지옥 가능)
  - 디버깅 어려움
  - 순서 보장 X
  - 에러 처리 복잡

2.5 비동기의 결과 처리

비동기 결과 받는 법:

1. 콜백 (Callback)
   - 완료 시 함수 호출
   - future.thenAccept(...)

2. Future / Promise
   - 미래 결과 객체
   - future.get() (블로킹) 또는 콜백

3. 이벤트 / 메시지
   - 완료 이벤트 발행
   - 리스너가 처리

4. Polling
   - 주기적으로 완료 확인

2.6 ILIC 의 비동기 처리

@Service
public class ShipmentAsyncService {
    
    // 비동기 처리
    public CompletableFuture<ShipmentResult> processAsync(Long id) {
        // 조회 + 처리를 비동기로
        return CompletableFuture
            .supplyAsync(() -> repository.findById(id))   // 1. 조회 (백그라운드)
            .thenApply(shipment -> {                       // 2. 완료 시 계산
                BigDecimal freight = calculateFreight(shipment);
                return new ShipmentResult(shipment, freight);
            });
        // 호출자는 즉시 반환받고 다른 일
    }
    
    // 여러 작업 동시 (비동기)
    public CompletableFuture<List<Shipment>> fetchManyAsync(List<Long> ids) {
        List<CompletableFuture<Shipment>> futures = ids.stream()
            .map(id -> CompletableFuture.supplyAsync(() -> repository.findById(id)))
            .toList();
        // 모두 동시 시작 (비동기)
        
        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .thenApply(v -> futures.stream()
                .map(CompletableFuture::join)
                .toList());
    }
}

2.7 자기 점검 답변

비동기의 정의는?

:
1. 정의:

  • A 완료 안 기다리고 B
  • 순서 무관
  1. 흐름:

    • A 시작 → B 시작 → ... → A 완료 통보
    • 비순차
  2. 특징:

    • 대기 시간 활용
    • 처리량 ↑
    • 복잡
  3. 결과:

    • 콜백
    • Future/Promise
    • 이벤트

3️⃣ 판단 기준 — 작업 순서

3.1 핵심 축

Sync/Async 의 축:

  "작업의 순서와 완료 통보"

질문:
  작업 A 의 완료를 확인하고
  다음 작업 B 로 넘어가는가?

  Yes → 동기
  No → 비동기

3.2 시각화 — 시간 흐름

동기:
시간 →
  [작업A 완료까지][작업B 완료까지][작업C]
  └─ A 완료 후 ─┘└─ B 완료 후 ─┘
  순차

비동기:
시간 →
  [작업A 시작]
  [작업B 시작]    ← A 완료 안 기다림
  [작업C 시작]
        ...
  [A 완료 → 콜백]
  [B 완료 → 콜백]
  비순차

3.3 순서 보장의 의미

순서 보장 (동기):

  작업이 정의된 순서대로 완료.
  A 완료 → B 완료 → C 완료

  예:
    데이터 조회 → 가공 → 저장
    각 단계가 이전 결과 필요
    순서 필수

순서 무관 (비동기):

  작업 완료 순서가 시작 순서와 다를 수 있음.
  A 시작, B 시작 → B 먼저 완료 가능

  예:
    독립적 API 호출 3개
    순서 무관

3.4 의존성과 순서

작업 간 의존성:

의존 있음 → 동기 적합:
  - B 가 A 의 결과 필요
  - 순서 필수
  - 예: 로그인 → 권한 조회

의존 없음 → 비동기 적합:
  - 독립적 작업
  - 순서 무관
  - 예: 여러 상품 정보 동시 조회

3.5 혼합 패턴

// 의존 있는 부분은 동기, 없는 부분은 비동기
public CompletableFuture<Report> generateReport(Long shipmentId) {
    return CompletableFuture
        .supplyAsync(() -> repository.findById(shipmentId))   // 1. 조회
        .thenCompose(shipment -> {
            // 2. 독립적 작업들 비동기 동시
            CompletableFuture<Tracking> tracking = fetchTrackingAsync(shipment);
            CompletableFuture<Invoice> invoice = fetchInvoiceAsync(shipment);
            CompletableFuture<Customs> customs = fetchCustomsAsync(shipment);
            
            // 3. 모두 완료 후 종합 (의존)
            return CompletableFuture.allOf(tracking, invoice, customs)
                .thenApply(v -> new Report(
                    shipment, tracking.join(), invoice.join(), customs.join()));
        });
}

3.6 ILIC 의 맥락

@Service
public class ShipmentOrderingService {
    
    // 의존 있음 — 동기 (순서 필수)
    public Shipment createShipment(ShipmentRequest request) {
        Company consignee = companyService.findById(request.getConsigneeId());  // 1
        validateConsignee(consignee);                                            // 2 (1 필요)
        Shipment shipment = buildShipment(request, consignee);                   // 3 (2 필요)
        return repository.save(shipment);                                        // 4 (3 필요)
        // 각 단계가 이전 결과 필요 → 동기
    }
    
    // 의존 없음 — 비동기 (순서 무관)
    public CompletableFuture<EnrichedShipment> enrichAsync(Shipment shipment) {
        CompletableFuture<Tracking> tracking = fetchTrackingAsync(shipment);
        CompletableFuture<Weather> weather = fetchWeatherAsync(shipment);
        // 독립적 → 동시 (비동기)
        
        return tracking.thenCombine(weather, 
            (t, w) -> new EnrichedShipment(shipment, t, w));
    }
}

3.7 자기 점검 답변

판단 기준은?

:
1. :

  • 작업 순서와 완료 통보
  1. 질문:

    • A 완료 확인 후 B?
    • Yes: 동기, No: 비동기
  2. 순서 보장:

    • 동기: 정의된 순서
    • 비동기: 무관
  3. 의존성:

    • 의존: 동기 적합
    • 독립: 비동기 적합

4️⃣ "완료 여부를 확인하는가?"

4.1 핵심 질문

Sync/Async 판단의 핵심 질문:

  "다음 작업을 위해
   이전 작업의 완료 여부를
   확인하는가?"

  Yes → 동기 (확인 후 진행)
  No → 비동기 (확인 없이 진행)

4.2 동기의 완료 확인

동기 — 완료 확인:

호출자: 작업 A 호출
  ↓
A 실행
  ↓
호출자: A 완료 확인 (대기 또는 polling)
  ↓ (완료됨)
호출자: 결과 받고 작업 B

확인 → 진행

4.3 비동기의 비확인

비동기 — 완료 비확인:

호출자: 작업 A 호출 (시작만)
  ↓
호출자: 작업 B (A 확인 X)
  ↓
호출자: 작업 C
  ...
A 완료 → 콜백/통보 (호출자가 등록한)

확인 안 함, 통보받음

4.4 시각화

동기 (확인):
  호출 ──→ [A 실행]
                ↑
  호출 ←── 완료 확인
  결과 받음
  ──→ 다음

비동기 (비확인):
  호출 ──→ [A 시작] (백그라운드)
  ──→ 다음 (확인 X)
  ──→ 다음
       ...
  [A 완료] ──→ 콜백 (통보)

4.5 통보의 방향

동기:
  - 호출자가 능동적으로 확인 (pull)
  - "끝났니?" 물어봄

비동기:
  - 작업이 능동적으로 통보 (push)
  - "끝났어!" 알려줌 (콜백)

핵심:
  - 동기: 호출자가 확인
  - 비동기: 작업이 통보

4.6 ILIC 의 맥락

public class CompletionCheck {
    
    // 동기 — 완료 확인 (결과 직접)
    public Shipment syncFetch(Long id) {
        Shipment result = repository.findById(id);   // 완료 확인 (결과 받음)
        return result;   // 확인 후 진행
    }
    
    // 비동기 — 통보 (콜백)
    public void asyncFetch(Long id, Consumer<Shipment> callback) {
        CompletableFuture
            .supplyAsync(() -> repository.findById(id))
            .thenAccept(callback);   // 완료 시 통보 (callback 호출)
        // 호출자는 확인 안 하고 진행
        // 완료 시 callback 자동
    }
    
    // 사용
    public void usage() {
        // 동기
        Shipment s = syncFetch(1L);   // 완료까지 (결과)
        process(s);
        
        // 비동기
        asyncFetch(2L, shipment -> {
            process(shipment);   // 완료 시 자동 (통보)
        });
        // 여기서 다른 일 (확인 X)
        doOtherWork();
    }
}

4.7 자기 점검 답변

"완료 여부를 확인하는가?" 의 의미는?

:
1. 핵심 질문:

  • 이전 작업 완료 확인 후 다음?
  • Yes: 동기, No: 비동기
  1. 동기:

    • 호출자 능동 확인 (pull)
    • 결과 직접
  2. 비동기:

    • 작업 능동 통보 (push)
    • 콜백
  3. 방향:

    • 동기: 확인
    • 비동기: 통보

5️⃣ DB 쿼리 비동기의 의미

5.1 "DB 쿼리를 비동기로"

"DB 쿼리를 비동기로 처리한다":

의미:
  - 쿼리 실행 후 결과를 기다리지 않음
  - 다른 작업 진행
  - 결과 준비되면 콜백/통보

대조 (동기 DB):
  - 쿼리 실행 후 결과까지 대기
  - 그 동안 스레드 블록
  - 결과 받고 진행

5.2 동기 DB 쿼리

// 전통적 동기 DB (JDBC, JPA)
public List<Shipment> findAllSync() {
    // 쿼리 실행 + 결과 대기
    List<Shipment> result = repository.findAll();
    // 이 줄에서 DB 응답까지 블록
    return result;
}

// 흐름:
// 1. 쿼리 전송
// 2. DB 처리 (대기)
// 3. 결과 수신
// 4. 반환
// → 스레드가 2-3 동안 블록

5.3 비동기 DB 쿼리

// 비동기 DB (R2DBC, 비동기 드라이버)
public CompletableFuture<List<Shipment>> findAllAsync() {
    return repository.findAllAsync();   // 즉시 반환 (Future)
    // 쿼리는 백그라운드
    // 스레드 블록 X
}

// 흐름:
// 1. 쿼리 전송
// 2. 즉시 Future 반환 (대기 X)
// 3. 스레드는 다른 일
// 4. DB 결과 준비 → 콜백/Future 완료

5.4 비동기 DB 의 효과

비동기 DB 의 효과:

동기:
  - 쿼리 100개 = 스레드 100개 필요 (각자 대기)
  - 또는 순차 (느림)

비동기:
  - 쿼리 100개 = 적은 스레드
  - 각 쿼리가 대기 안 함
  - 스레드 재사용
  - 처리량 ↑

예 (Reactive):
  - WebFlux + R2DBC
  - 적은 스레드로 많은 요청

5.5 자바의 DB 비동기 옵션

// 1. CompletableFuture + 스레드 풀 (의사 비동기)
public CompletableFuture<List<Shipment>> findAllAsync1() {
    return CompletableFuture.supplyAsync(() -> 
        repository.findAll(),   // 동기 쿼리를 별도 스레드
        ioExecutor);
    // 진짜 비동기 X (스레드가 블록, 단 호출 스레드는 자유)
}

// 2. R2DBC (진짜 비동기)
public Flux<Shipment> findAllReactive() {
    return r2dbcRepository.findAll();   // 논블로킹 드라이버
    // 진짜 비동기 I/O
}

// 3. Virtual Thread (Java 21+)
public List<Shipment> findAllVirtual() {
    // 가상 스레드에서 동기 쿼리
    // I/O 대기 시 OS 스레드 양보
    // 동기 코드, 비동기 효과
    return repository.findAll();
}

5.6 ILIC 의 맥락

@Service
public class ShipmentDbService {
    
    private final ExecutorService ioExecutor;
    
    // 동기 (전통)
    public Shipment findSync(Long id) {
        return repository.findById(id);   // DB 응답까지 블록
    }
    
    // 비동기 (CompletableFuture)
    public CompletableFuture<Shipment> findAsync(Long id) {
        return CompletableFuture.supplyAsync(
            () -> repository.findById(id),
            ioExecutor);
        // 호출 스레드는 자유
    }
    
    // 여러 조회 동시 (비동기 효과)
    public CompletableFuture<List<Shipment>> findManyAsync(List<Long> ids) {
        List<CompletableFuture<Shipment>> futures = ids.stream()
            .map(this::findAsync)
            .toList();
        // 동시 시작
        
        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .thenApply(v -> futures.stream().map(CompletableFuture::join).toList());
        // 순차 100개 vs 동시 100개
        // 비동기가 처리량 ↑
    }
}

5.7 자기 점검 답변

"DB 쿼리를 비동기로" 의 의미는?

:
1. 의미:

  • 쿼리 후 결과 안 기다림
  • 다른 작업
  • 콜백/통보
  1. 동기 DB:

    • 결과까지 블록
    • 스레드 대기
  2. 비동기 DB:

    • 즉시 반환 (Future)
    • 스레드 자유
    • 처리량 ↑
  3. 자바 옵션:

    • CompletableFuture
    • R2DBC (진짜)
    • Virtual Thread

6️⃣ 비동기가 항상 빠른가

6.1 핵심 질문

질문:
  비동기가 항상 더 빠른가?

답:
  아니다.
  - 단일 작업 시간 (latency): 같거나 느림
  - 시스템 처리량 (throughput): 향상

6.2 Latency vs Throughput

Latency (지연 시간):
  - 단일 작업의 처리 시간
  - 요청 → 응답

Throughput (처리량):
  - 단위 시간당 처리 작업 수
  - 초당 요청 수 (RPS)

비동기:
  - Latency: 개선 안 됨 (또는 약간 나쁨)
  - Throughput: 개선됨

6.3 단일 작업의 비교

단일 작업 (DB 조회 100ms):

동기:
  - 100ms (조회 시간)

비동기:
  - 100ms (조회 시간 같음)
  - + 콜백 오버헤드
  - 약간 느릴 수도

핵심:
  - 비동기가 조회 자체를 빠르게 X
  - 같은 작업, 같은 시간

6.4 여러 작업의 비교

100개 작업 (각 100ms, I/O 대기):

동기 순차 (1 스레드):
  - 100 × 100ms = 10초

동기 병렬 (100 스레드):
  - 100ms (동시)
  - 단, 100 스레드 필요

비동기 (적은 스레드):
  - 약 100ms (동시 효과)
  - 적은 스레드 (효율)

핵심:
  - 비동기 = 적은 자원으로 처리량 ↑

6.5 시각화

동기 순차:
  [작업1 100ms][작업2 100ms][작업3 100ms]...
  총 시간 = 작업 수 × 시간

비동기 (동시):
  [작업1 ─────]
  [작업2 ─────]   모두 동시
  [작업3 ─────]
  총 시간 ≈ 가장 긴 작업

처리량:
  - 동기: 낮음 (순차)
  - 비동기: 높음 (동시)

6.6 언제 비동기가 유리한가

비동기가 유리:
  ✓ I/O 바운드 (대기 많음)
  ✓ 많은 동시 요청
  ✓ 독립적 작업
  ✓ 처리량 중요

비동기가 불리:
  ✗ CPU 바운드 (계산만)
  ✗ 단일 작업
  ✗ 순서 의존
  ✗ 단순함 우선

예:
  - 웹 서버 (많은 요청): 비동기 ✓
  - 단일 계산: 동기 ✓

6.7 ILIC 의 맥락

public class AsyncPerformance {
    
    // 단일 작업 — 동기로 충분
    public Shipment single(Long id) {
        return repository.findById(id);
        // 비동기로 해도 빨라지지 X
    }
    
    // 여러 작업 — 비동기 유리
    public List<Shipment> manySync(List<Long> ids) {
        // 순차 — 느림 (100개 = 10초)
        return ids.stream()
            .map(repository::findById)
            .toList();
    }
    
    public CompletableFuture<List<Shipment>> manyAsync(List<Long> ids) {
        // 동시 — 빠름 (100개 ≈ 0.1초)
        List<CompletableFuture<Shipment>> futures = ids.stream()
            .map(id -> CompletableFuture.supplyAsync(() -> repository.findById(id)))
            .toList();
        
        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .thenApply(v -> futures.stream().map(CompletableFuture::join).toList());
        // 처리량 ↑ (latency 는 단일과 비슷)
    }
}

6.8 자기 점검 답변

비동기가 항상 빠른가?

:
1. 아니다:

  • 단일 작업: 같거나 느림
  • 처리량: 향상
  1. Latency vs Throughput:

    • Latency (단일): 개선 X
    • Throughput (처리량): 개선
  2. 여러 작업:

    • 동기 순차: 느림
    • 비동기 동시: 빠름 (처리량)
  3. 유리한 경우:

    • I/O 바운드
    • 많은 요청
    • 독립 작업

7️⃣ 콜백으로 결과 받기

7.1 콜백의 정의

콜백 (Callback):

  작업 완료 시 호출되는 함수.
  비동기 결과를 받는 방법.

흐름:
  1. 작업 시작 + 콜백 등록
  2. 작업 진행 (백그라운드)
  3. 완료 → 콜백 자동 호출

7.2 콜백 예시

// 콜백 등록
public void fetchWithCallback(Long id, Consumer<Shipment> onSuccess, 
                              Consumer<Throwable> onError) {
    CompletableFuture
        .supplyAsync(() -> repository.findById(id))
        .thenAccept(onSuccess)        // 성공 콜백
        .exceptionally(error -> {     // 실패 콜백
            onError.accept(error);
            return null;
        });
}

// 사용
fetchWithCallback(1L,
    shipment -> System.out.println("Found: " + shipment),  // 성공 시
    error -> System.err.println("Error: " + error)          // 실패 시
);

7.3 콜백 지옥

// ❌ 콜백 지옥 (Callback Hell)
fetchUser(userId, user -> {
    fetchOrders(user.getId(), orders -> {
        fetchShipments(orders, shipments -> {
            fetchTracking(shipments, tracking -> {
                // 깊은 중첩
                // 가독성 ↓
                // 에러 처리 복잡
            });
        });
    });
});

// ✓ CompletableFuture 체이닝
fetchUserAsync(userId)
    .thenCompose(user -> fetchOrdersAsync(user.getId()))
    .thenCompose(orders -> fetchShipmentsAsync(orders))
    .thenCompose(shipments -> fetchTrackingAsync(shipments))
    .thenAccept(tracking -> process(tracking))
    .exceptionally(error -> { handle(error); return null; });
// 평탄, 가독성 ↑

7.4 Future 방식

// Future — 미래 결과
public void futureExample() throws Exception {
    Future<Shipment> future = executor.submit(() -> repository.findById(1L));
    
    // 다른 일
    doOtherWork();
    
    // 결과 받기 (블로킹)
    Shipment result = future.get();   // 완료까지 대기
    // 또는 future.get(5, SECONDS) 타임아웃
}

// CompletableFuture — 콜백 가능
public void completableExample() {
    CompletableFuture<Shipment> future = 
        CompletableFuture.supplyAsync(() -> repository.findById(1L));
    
    future.thenAccept(this::process);   // 콜백 (블로킹 X)
}

7.5 결과 받는 방법 비교

방법블로킹가독성예시
콜백X중첩 시 ↓thenAccept
Future.getO단순future.get()
CompletableFuture 체이닝X평탄thenCompose
Reactive (Flux/Mono)X선언적subscribe

7.6 ILIC 의 맥락

@Service
public class ShipmentCallbackService {
    
    // 콜백 패턴
    public void processWithCallback(Long id,
                                     Consumer<ShipmentResult> onComplete) {
        CompletableFuture
            .supplyAsync(() -> repository.findById(id))
            .thenApply(this::enrich)
            .thenApply(this::calculate)
            .thenAccept(onComplete)   // 완료 시 콜백
            .exceptionally(error -> {
                log.error("Processing failed", error);
                return null;
            });
    }
    
    // 체이닝 (콜백 지옥 회피)
    public CompletableFuture<Report> generateReport(Long id) {
        return CompletableFuture
            .supplyAsync(() -> repository.findById(id))
            .thenCompose(this::fetchTrackingAsync)
            .thenCompose(this::fetchInvoiceAsync)
            .thenApply(this::buildReport);
        // 평탄한 체이닝
    }
}

7.7 자기 점검 답변

콜백으로 결과 받기는?

:
1. 콜백:

  • 완료 시 호출 함수
  • 비동기 결과
  1. 콜백 지옥:

    • 깊은 중첩
    • CompletableFuture 체이닝으로 해결
  2. 방법:

    • 콜백 (thenAccept)
    • Future.get (블로킹)
    • 체이닝 (thenCompose)
    • Reactive
  3. 권장:

    • 체이닝 (평탄)
    • 예외 처리 (exceptionally)

8️⃣ Sync/Async vs Blocking/Non-Blocking

8.1 두 개의 다른 축

두 개의 독립적 축:

축 1: Sync/Async (작업 순서)
  - 완료 확인하고 다음?
  - 작업 순서와 통보

축 2: Blocking/Non-Blocking (제어권)
  - 호출 시 제어권 대기?
  - 다음 Unit (2.2)

두 축은 독립적
→ 4가지 조합 (Unit 2.3)

8.2 흔한 혼동

흔한 혼동:

"동기 = 블로킹, 비동기 = 논블로킹"
→ 부정확!

실제:
  - 동기 + 블로킹 (전통 IO)
  - 동기 + 논블로킹 (polling)
  - 비동기 + 블로킹 (Future.get)
  - 비동기 + 논블로킹 (CompletableFuture)

→ 4가지 모두 가능

8.3 축의 구분

Sync/Async 축:
  관심사: 작업 완료 통보
  질문: 결과를 언제 어떻게 받나?
    - 동기: 즉시 (확인)
    - 비동기: 나중에 (통보)

Blocking/Non-Blocking 축:
  관심사: 제어권
  질문: 호출 시 대기하나?
    - 블로킹: 대기 (제어권 뺏김)
    - 논블로킹: 즉시 반환 (제어권 유지)

8.4 미리보기 — 4분면

            Blocking      Non-Blocking
Sync     │ 전통 IO      │ NIO Polling   │
         │ Socket.read  │ read() == 0    │
─────────┼──────────────┼────────────────┤
Async    │ Future.get   │ CompletableFuture │
         │              │ Callback        │

Unit 2.3 에서 정밀

8.5 다음 Unit 예고

Unit 2.2 — Blocking vs Non-Blocking:
  - 제어권 축
  - 블로킹: 대기
  - 논블로킹: 즉시 반환

Unit 2.3 — 4분면 매트릭스 (마스터):
  - 4가지 조합
  - 각 예시
  - 실무 권장

8.6 ILIC 의 맥락 (미리보기)

public class AxisExample {
    
    // 동기 + 블로킹 (전통)
    public Shipment syncBlocking(Long id) {
        return repository.findById(id);   // 결과까지 블록
    }
    
    // 비동기 + 블로킹 (Future.get)
    public Shipment asyncBlocking(Long id) throws Exception {
        Future<Shipment> future = executor.submit(() -> repository.findById(id));
        return future.get();   // 비동기 시작, 결과는 블로킹
    }
    
    // 비동기 + 논블로킹 (CompletableFuture)
    public CompletableFuture<Shipment> asyncNonBlocking(Long id) {
        return CompletableFuture.supplyAsync(() -> repository.findById(id));
        // 비동기 + 콜백 (블로킹 X)
    }
}

8.7 자기 점검 답변

Sync/Async vs Blocking/Non-Blocking 의 차이는?

:
1. 두 개의 축:

  • Sync/Async: 작업 순서/통보
  • Blocking/Non-Blocking: 제어권
  1. 독립적:

    • 4가지 조합 가능
    • "동기 = 블로킹" 부정확
  2. Sync/Async:

    • 결과 언제 받나
    • 확인 vs 통보
  3. Blocking/Non-Blocking:

    • 제어권 대기 여부
    • 다음 Unit

9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
동기?완료 확인 후 다음 (순서)
비동기?완료 안 기다리고 다음 (통보)
판단 기준?작업 순서/완료 확인
핵심 질문?완료 여부 확인?
DB 비동기?결과 안 기다림, 콜백
비동기 항상 빠른가?아니다 (처리량만)
Latency vs Throughput?단일 시간 vs 처리량
콜백?완료 시 호출 함수
콜백 지옥?중첩, 체이닝으로 해결
Sync/Async vs Block?다른 축 (순서 vs 제어권)

9.2 자기 점검 체크리스트

동기

  • 정의 (완료 확인 후)
  • 순서 보장
  • 결과 직접

비동기

  • 정의 (완료 안 기다림)
  • 통보 (콜백)
  • 처리량

판단

  • 작업 순서 축
  • 완료 확인 질문

DB

  • 동기 vs 비동기 DB
  • R2DBC, Virtual Thread

성능

  • Latency vs Throughput
  • 비동기 항상 빠르지 않음

콜백

  • 콜백 패턴
  • 콜백 지옥
  • 체이닝

두 축

  • Sync/Async vs Block
  • 독립적

9.3 추가 심화 질문

Q1: Promise 와 Future 차이?

답:

  • Future: 읽기 전용 미래 결과 (자바 Future)
  • Promise: 쓰기 가능 (값 설정)
  • 자바 CompletableFuture: Future + Promise 역할
  • complete() 로 값 설정 가능

Q2: 비동기 예외 처리?

답:

future
    .thenApply(this::process)
    .exceptionally(error -> {   // 예외 처리
        log.error("Failed", error);
        return defaultValue;
    });
// 또는 handle (성공/실패 모두)
.handle((result, error) -> {
    if (error != null) return fallback;
    return result;
});

Q3: 동기 코드를 비동기로?

답:

  • CompletableFuture.supplyAsync(동기 작업, executor)
  • 동기 작업을 별도 스레드
  • 호출 스레드는 자유
  • 단, 작업 스레드는 여전히 블록 (의사 비동기)

Q4: Reactive Programming?

답:

  • 비동기 + 데이터 스트림
  • Flux (0~N), Mono (0~1)
  • 선언적 (map, filter)
  • 백프레셔 (backpressure)
  • Spring WebFlux

Q5: async/await (다른 언어)?

답:

  • JavaScript, C#, Python 등
  • 비동기를 동기처럼 작성
  • 가독성 ↑
  • 자바는 없음 (CompletableFuture, Virtual Thread)

🎯 핵심 요약 — 3줄 정리

1. Sync vs Async

  • 동기: 완료 확인 후 다음 (순서)
  • 비동기: 완료 안 기다림 + 통보 (콜백)

2. 판단

  • 핵심: 완료 여부 확인?
  • 작업 순서/통보 축

3. 성능

  • 비동기 ≠ 항상 빠름
  • Latency 동일, Throughput 향상
  • 두 축 (Sync/Async vs Block) 독립

📚 다음으로...

Unit 2.2 — Blocking vs Non-Blocking (제어권 관점)

이번 Unit에서 Sync/Async (작업 순서) 를 봤다면, 다음은 제어권 축.

  • 블로킹의 정의
  • 논블로킹의 정의
  • 제어권의 의미
  • polling, callback

Phase 2 진행 상황

🚀 Phase 2 — Sync/Async × Blocking/Non-Blocking 4분면
  ✅ Unit 2.1 Sync vs Async (작업 순서) ← 여기
  ⏭ Unit 2.2 Blocking vs Non-Blocking (제어권)
  ⏭ Unit 2.3 4분면 매트릭스 (★ 마스터)

4주차 누적 진행

✅ Phase 1 — 동시성의 기초 (4 Unit)
🚀 Phase 2 — 4분면 매트릭스 (1/3 진행)

총: 5/35 Unit
profile
Software Developer

0개의 댓글