F-LAB JAVA · 4주차 · Phase 2 · Sync/Async × Blocking/Non-Blocking 4분면
🚀 Phase 2 시작 — 4분면 매트릭스 진입
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
동기 (Sync) 와 비동기 (Async) 는 "작업의 순서와 완료 통보" 를 다루는 축으로, 호출자가 이전 작업의 완료 여부를 확인하고 다음으로 넘어가는가 가 판단 기준이다.
동기 (Synchronous) 는 작업 A 의 완료를 확인한 후 B 를 실행 (순서 보장), 비동기 (Asynchronous) 는 A 의 완료를 기다리지 않고 B 를 실행 (순서 무관, 완료는 나중에 통보/콜백).
핵심 질문: "다음 작업을 위해 이전 작업의 완료 여부를 확인하는가?" — Yes 면 동기, No 면 비동기.
비동기가 항상 빠른 것은 아니다 — 단일 작업의 처리 시간 (latency) 은 같거나 오히려 느릴 수 있고, 시스템 전체의 처리량 (throughput) 이 향상되는 것.
비동기 결과는 콜백 (callback) 또는 Future/Promise 로 받으며, 이 축은 블로킹/논블로킹 (제어권 축) 과는 다른 차원이다 (다음 Unit).
동기 (Sync):
카운터에서 주문하고 음식 나올 때까지 기다림
- 주문 (작업 A)
- 완료 확인 (음식 나옴) 후
- 자리로 (작업 B)
- 순서 보장
비동기 (Async):
진동벨 받고 자리로
- 주문 (작업 A)
- 완료 안 기다리고 자리로 (작업 B)
- 진동벨 울리면 (콜백) 가지러
- 순서 무관
핵심:
- 동기: 완료 확인 후 다음
- 비동기: 완료 안 기다리고 다음, 나중에 통보
→ 동기 = 완료 확인 후 진행, 비동기 = 완료 안 기다림 + 나중 통보.
1. 동기 (Synchronous) 의 정의
2. 비동기 (Asynchronous) 의 정의
3. 판단 기준 — 작업 순서
4. "완료 여부를 확인하는가?"
5. DB 쿼리 비동기의 의미
6. 비동기가 항상 빠른가
7. 콜백으로 결과 받기
8. Sync/Async vs Blocking/Non-Blocking
9. 면접 + 자기 점검
동기 (Synchronous):
작업 A 의 완료를 확인한 후
다음 작업 B 를 실행하는 방식.
핵심:
- 순서 보장
- 완료 확인
- 결과를 직접 받음
동기 처리 흐름:
호출자:
1. 작업 A 호출
2. A 완료 대기 (또는 확인)
3. A 결과 받음
4. 작업 B 시작
5. ...
순차적
A → B → C
// 동기 처리
public void syncExample() {
String data = fetchData(); // 1. 완료까지 대기
String processed = process(data); // 2. data 받은 후 처리
save(processed); // 3. 순차
// 각 단계가 완료된 후 다음
// 순서 보장
}
private String fetchData() {
// 데이터 조회 (완료까지)
return "data";
}
동기의 특징:
장점:
- 단순, 직관적
- 순서 보장
- 디버깅 쉬움
- 결과 직접 받음
단점:
- 대기 시간 (완료까지)
- 자원 비효율 (대기 중 놀음)
- 처리량 한계
주의 — 동기 ≠ 블로킹:
동기 (Sync):
- 작업 순서 축
- 완료 확인
블로킹 (Blocking):
- 제어권 축
- 대기 여부
흔히 혼동하지만 다른 개념.
다음 Unit (2.2) 에서 정밀.
대부분의 동기는 블로킹이지만
동기 + 논블로킹도 가능 (polling)
@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. 정의:
흐름:
특징:
주의:
비동기 (Asynchronous):
작업 A 의 완료를 기다리지 않고
다음 작업 B 를 실행하는 방식.
핵심:
- 순서 무관
- 완료 안 기다림
- 완료는 나중에 통보 (콜백/Future)
비동기 처리 흐름:
호출자:
1. 작업 A 호출 (시작만)
2. A 완료 안 기다림
3. 작업 B 시작 (A 와 무관)
...
N. A 완료 시 → 콜백/통보
비순차적
A 시작 → B 시작 → ... → A 완료 통보
// 비동기 처리
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";
});
}
비동기의 특징:
장점:
- 대기 시간 활용
- 자원 효율
- 처리량 ↑
- 응답성 ↑
단점:
- 복잡 (콜백 지옥 가능)
- 디버깅 어려움
- 순서 보장 X
- 에러 처리 복잡
비동기 결과 받는 법:
1. 콜백 (Callback)
- 완료 시 함수 호출
- future.thenAccept(...)
2. Future / Promise
- 미래 결과 객체
- future.get() (블로킹) 또는 콜백
3. 이벤트 / 메시지
- 완료 이벤트 발행
- 리스너가 처리
4. Polling
- 주기적으로 완료 확인
@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());
}
}
비동기의 정의는?
답:
1. 정의:
흐름:
특징:
결과:
Sync/Async 의 축:
"작업의 순서와 완료 통보"
질문:
작업 A 의 완료를 확인하고
다음 작업 B 로 넘어가는가?
Yes → 동기
No → 비동기
동기:
시간 →
[작업A 완료까지][작업B 완료까지][작업C]
└─ A 완료 후 ─┘└─ B 완료 후 ─┘
순차
비동기:
시간 →
[작업A 시작]
[작업B 시작] ← A 완료 안 기다림
[작업C 시작]
...
[A 완료 → 콜백]
[B 완료 → 콜백]
비순차
순서 보장 (동기):
작업이 정의된 순서대로 완료.
A 완료 → B 완료 → C 완료
예:
데이터 조회 → 가공 → 저장
각 단계가 이전 결과 필요
순서 필수
순서 무관 (비동기):
작업 완료 순서가 시작 순서와 다를 수 있음.
A 시작, B 시작 → B 먼저 완료 가능
예:
독립적 API 호출 3개
순서 무관
작업 간 의존성:
의존 있음 → 동기 적합:
- B 가 A 의 결과 필요
- 순서 필수
- 예: 로그인 → 권한 조회
의존 없음 → 비동기 적합:
- 독립적 작업
- 순서 무관
- 예: 여러 상품 정보 동시 조회
// 의존 있는 부분은 동기, 없는 부분은 비동기
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()));
});
}
@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));
}
}
판단 기준은?
답:
1. 축:
질문:
순서 보장:
의존성:
Sync/Async 판단의 핵심 질문:
"다음 작업을 위해
이전 작업의 완료 여부를
확인하는가?"
Yes → 동기 (확인 후 진행)
No → 비동기 (확인 없이 진행)
동기 — 완료 확인:
호출자: 작업 A 호출
↓
A 실행
↓
호출자: A 완료 확인 (대기 또는 polling)
↓ (완료됨)
호출자: 결과 받고 작업 B
확인 → 진행
비동기 — 완료 비확인:
호출자: 작업 A 호출 (시작만)
↓
호출자: 작업 B (A 확인 X)
↓
호출자: 작업 C
...
A 완료 → 콜백/통보 (호출자가 등록한)
확인 안 함, 통보받음
동기 (확인):
호출 ──→ [A 실행]
↑
호출 ←── 완료 확인
결과 받음
──→ 다음
비동기 (비확인):
호출 ──→ [A 시작] (백그라운드)
──→ 다음 (확인 X)
──→ 다음
...
[A 완료] ──→ 콜백 (통보)
동기:
- 호출자가 능동적으로 확인 (pull)
- "끝났니?" 물어봄
비동기:
- 작업이 능동적으로 통보 (push)
- "끝났어!" 알려줌 (콜백)
핵심:
- 동기: 호출자가 확인
- 비동기: 작업이 통보
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();
}
}
"완료 여부를 확인하는가?" 의 의미는?
답:
1. 핵심 질문:
동기:
비동기:
방향:
"DB 쿼리를 비동기로 처리한다":
의미:
- 쿼리 실행 후 결과를 기다리지 않음
- 다른 작업 진행
- 결과 준비되면 콜백/통보
대조 (동기 DB):
- 쿼리 실행 후 결과까지 대기
- 그 동안 스레드 블록
- 결과 받고 진행
// 전통적 동기 DB (JDBC, JPA)
public List<Shipment> findAllSync() {
// 쿼리 실행 + 결과 대기
List<Shipment> result = repository.findAll();
// 이 줄에서 DB 응답까지 블록
return result;
}
// 흐름:
// 1. 쿼리 전송
// 2. DB 처리 (대기)
// 3. 결과 수신
// 4. 반환
// → 스레드가 2-3 동안 블록
// 비동기 DB (R2DBC, 비동기 드라이버)
public CompletableFuture<List<Shipment>> findAllAsync() {
return repository.findAllAsync(); // 즉시 반환 (Future)
// 쿼리는 백그라운드
// 스레드 블록 X
}
// 흐름:
// 1. 쿼리 전송
// 2. 즉시 Future 반환 (대기 X)
// 3. 스레드는 다른 일
// 4. DB 결과 준비 → 콜백/Future 완료
비동기 DB 의 효과:
동기:
- 쿼리 100개 = 스레드 100개 필요 (각자 대기)
- 또는 순차 (느림)
비동기:
- 쿼리 100개 = 적은 스레드
- 각 쿼리가 대기 안 함
- 스레드 재사용
- 처리량 ↑
예 (Reactive):
- WebFlux + R2DBC
- 적은 스레드로 많은 요청
// 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();
}
@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개
// 비동기가 처리량 ↑
}
}
"DB 쿼리를 비동기로" 의 의미는?
답:
1. 의미:
동기 DB:
비동기 DB:
자바 옵션:
질문:
비동기가 항상 더 빠른가?
답:
아니다.
- 단일 작업 시간 (latency): 같거나 느림
- 시스템 처리량 (throughput): 향상
Latency (지연 시간):
- 단일 작업의 처리 시간
- 요청 → 응답
Throughput (처리량):
- 단위 시간당 처리 작업 수
- 초당 요청 수 (RPS)
비동기:
- Latency: 개선 안 됨 (또는 약간 나쁨)
- Throughput: 개선됨
단일 작업 (DB 조회 100ms):
동기:
- 100ms (조회 시간)
비동기:
- 100ms (조회 시간 같음)
- + 콜백 오버헤드
- 약간 느릴 수도
핵심:
- 비동기가 조회 자체를 빠르게 X
- 같은 작업, 같은 시간
100개 작업 (각 100ms, I/O 대기):
동기 순차 (1 스레드):
- 100 × 100ms = 10초
동기 병렬 (100 스레드):
- 100ms (동시)
- 단, 100 스레드 필요
비동기 (적은 스레드):
- 약 100ms (동시 효과)
- 적은 스레드 (효율)
핵심:
- 비동기 = 적은 자원으로 처리량 ↑
동기 순차:
[작업1 100ms][작업2 100ms][작업3 100ms]...
총 시간 = 작업 수 × 시간
비동기 (동시):
[작업1 ─────]
[작업2 ─────] 모두 동시
[작업3 ─────]
총 시간 ≈ 가장 긴 작업
처리량:
- 동기: 낮음 (순차)
- 비동기: 높음 (동시)
비동기가 유리:
✓ I/O 바운드 (대기 많음)
✓ 많은 동시 요청
✓ 독립적 작업
✓ 처리량 중요
비동기가 불리:
✗ CPU 바운드 (계산만)
✗ 단일 작업
✗ 순서 의존
✗ 단순함 우선
예:
- 웹 서버 (많은 요청): 비동기 ✓
- 단일 계산: 동기 ✓
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 는 단일과 비슷)
}
}
비동기가 항상 빠른가?
답:
1. 아니다:
Latency vs Throughput:
여러 작업:
유리한 경우:
콜백 (Callback):
작업 완료 시 호출되는 함수.
비동기 결과를 받는 방법.
흐름:
1. 작업 시작 + 콜백 등록
2. 작업 진행 (백그라운드)
3. 완료 → 콜백 자동 호출
// 콜백 등록
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) // 실패 시
);
// ❌ 콜백 지옥 (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; });
// 평탄, 가독성 ↑
// 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)
}
| 방법 | 블로킹 | 가독성 | 예시 |
|---|---|---|---|
| 콜백 | X | 중첩 시 ↓ | thenAccept |
| Future.get | O | 단순 | future.get() |
| CompletableFuture 체이닝 | X | 평탄 | thenCompose |
| Reactive (Flux/Mono) | X | 선언적 | subscribe |
@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);
// 평탄한 체이닝
}
}
콜백으로 결과 받기는?
답:
1. 콜백:
콜백 지옥:
방법:
권장:
두 개의 독립적 축:
축 1: Sync/Async (작업 순서)
- 완료 확인하고 다음?
- 작업 순서와 통보
축 2: Blocking/Non-Blocking (제어권)
- 호출 시 제어권 대기?
- 다음 Unit (2.2)
두 축은 독립적
→ 4가지 조합 (Unit 2.3)
흔한 혼동:
"동기 = 블로킹, 비동기 = 논블로킹"
→ 부정확!
실제:
- 동기 + 블로킹 (전통 IO)
- 동기 + 논블로킹 (polling)
- 비동기 + 블로킹 (Future.get)
- 비동기 + 논블로킹 (CompletableFuture)
→ 4가지 모두 가능
Sync/Async 축:
관심사: 작업 완료 통보
질문: 결과를 언제 어떻게 받나?
- 동기: 즉시 (확인)
- 비동기: 나중에 (통보)
Blocking/Non-Blocking 축:
관심사: 제어권
질문: 호출 시 대기하나?
- 블로킹: 대기 (제어권 뺏김)
- 논블로킹: 즉시 반환 (제어권 유지)
Blocking Non-Blocking
Sync │ 전통 IO │ NIO Polling │
│ Socket.read │ read() == 0 │
─────────┼──────────────┼────────────────┤
Async │ Future.get │ CompletableFuture │
│ │ Callback │
Unit 2.3 에서 정밀
Unit 2.2 — Blocking vs Non-Blocking:
- 제어권 축
- 블로킹: 대기
- 논블로킹: 즉시 반환
Unit 2.3 — 4분면 매트릭스 (마스터):
- 4가지 조합
- 각 예시
- 실무 권장
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)
}
}
Sync/Async vs Blocking/Non-Blocking 의 차이는?
답:
1. 두 개의 축:
독립적:
Sync/Async:
Blocking/Non-Blocking:
| Q | 핵심 답변 |
|---|---|
| 동기? | 완료 확인 후 다음 (순서) |
| 비동기? | 완료 안 기다리고 다음 (통보) |
| 판단 기준? | 작업 순서/완료 확인 |
| 핵심 질문? | 완료 여부 확인? |
| DB 비동기? | 결과 안 기다림, 콜백 |
| 비동기 항상 빠른가? | 아니다 (처리량만) |
| Latency vs Throughput? | 단일 시간 vs 처리량 |
| 콜백? | 완료 시 호출 함수 |
| 콜백 지옥? | 중첩, 체이닝으로 해결 |
| Sync/Async vs Block? | 다른 축 (순서 vs 제어권) |
답:
답:
future
.thenApply(this::process)
.exceptionally(error -> { // 예외 처리
log.error("Failed", error);
return defaultValue;
});
// 또는 handle (성공/실패 모두)
.handle((result, error) -> {
if (error != null) return fallback;
return result;
});
답:
답:
답:
1. Sync vs Async
2. 판단
3. 성능
이번 Unit에서 Sync/Async (작업 순서) 를 봤다면, 다음은 제어권 축.
🚀 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분면 매트릭스 (★ 마스터)
✅ Phase 1 — 동시성의 기초 (4 Unit)
🚀 Phase 2 — 4분면 매트릭스 (1/3 진행)
총: 5/35 Unit