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

Psj·2026년 5월 20일

F-lab

목록 보기
125/239

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

F-LAB JAVA · 4주차 · Phase 2 · Sync/Async × Blocking/Non-Blocking 4분면


📌 학습 목표

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

  • 블로킹 (Blocking) 의 정의와 핵심은?
  • 논블로킹 (Non-Blocking) 의 정의와 핵심은?
  • 제어권 (Control) 의 정확한 의미는?
  • "제어권을 가져갔는가?" 라는 판단 기준은?
  • Sync 와 Blocking 의 차이 를 한 문장으로?
  • 논블로킹에서 결과를 받는 법 (polling, callback) 은?
  • 블로킹의 스레드 상태 는?
  • 논블로킹 I/O 의 동작은?
  • 블로킹/논블로킹의 실무 영향 은?

🎯 핵심 한 문장

블로킹 (Blocking) 과 논블로킹 (Non-Blocking) 은 "제어권 (control)" 을 다루는 축으로, 호출된 함수가 끝날 때까지 호출자가 제어권을 잃고 대기하는가 가 판단 기준이다.
블로킹 은 호출된 함수가 완료될 때까지 호출자가 제어권을 잃고 대기 (그 스레드는 "할 일이 없는 상태"), 논블로킹 은 호출 즉시 제어권을 반환 하여 호출자가 다른 일을 할 수 있다.
핵심 질문: "OS (또는 호출된 함수) 가 제어권을 가져갔는가, 즉시 돌려줬는가?" — 가져갔으면 블로킹, 즉시 돌려줬으면 논블로킹.
Sync (작업 순서 축) 와 Blocking (제어권 축) 은 다르다 — Sync 는 "완료 확인", Blocking 은 "제어권 대기" 이며, 동기+논블로킹 (polling) 처럼 조합이 가능하다.
논블로킹에서 결과를 받으려면 polling (반복 확인) 또는 callback (완료 통보) 을 사용한다.

비유 — 세탁소 맡기기

블로킹:
  세탁소에서 옷 세탁 끝날 때까지 기다림
  - 옷 맡김 (호출)
  - 세탁 끝날 때까지 서 있음 (제어권 잃음)
  - 다른 일 못 함
  - 완료 후 옷 받고 떠남

논블로킹:
  옷 맡기고 바로 나옴
  - 옷 맡김 (호출)
  - 즉시 나옴 (제어권 유지)
  - 다른 일 (장보기 등)
  - polling: 가끔 들러서 "다 됐어요?"
  - callback: 다 되면 전화 줌

핵심:
  - 블로킹: 끝날 때까지 묶임 (제어권 잃음)
  - 논블로킹: 바로 자유 (제어권 유지)

→ 블로킹 = 제어권 잃고 대기, 논블로킹 = 제어권 유지.


🧭 9개 섹션 로드맵

1. 블로킹의 정의
2. 논블로킹의 정의
3. 제어권의 의미
4. "제어권을 가져갔는가?"
5. Sync와 Blocking의 차이
6. 논블로킹에서 결과 받기 (polling/callback)
7. 블로킹의 스레드 상태
8. 논블로킹 I/O의 동작
9. 면접 + 자기 점검

1️⃣ 블로킹의 정의

1.1 블로킹의 정의

블로킹 (Blocking):

  호출된 함수가 완료될 때까지
  호출자가 제어권을 잃고 대기하는 것.

핵심:
  - 제어권 넘어감
  - 호출자 대기
  - 완료까지 멈춤

1.2 블로킹의 흐름

블로킹 흐름:

호출자 스레드:
  1. 함수 호출
  2. 제어권 함수로 넘김
  3. 대기 (할 일 없음)
  ...
  4. 함수 완료
  5. 제어권 돌아옴
  6. 다음 코드 진행

대기 중 = 블록됨

1.3 코드 예시

// 블로킹 호출
public void blockingExample() {
    System.out.println("Before");
    
    String data = blockingRead();   // ★ 완료까지 대기 (블록)
    // 이 줄에서 멈춤
    // 제어권 read 로
    
    System.out.println("After: " + data);   // read 완료 후
}

private String blockingRead() {
    // 예: 소켓 읽기, DB 조회, 파일 읽기
    // 데이터 올 때까지 대기
    return socket.read();   // 데이터 없으면 계속 대기
}

1.4 블로킹의 예

블로킹 작업의 예:

1. 전통적 I/O
   - InputStream.read()
   - Socket.read()
   - 데이터 없으면 대기

2. 동기 DB
   - JDBC 쿼리
   - 결과까지 대기

3. Future.get()
   - 작업 완료까지 대기

4. Thread.join()
   - 스레드 종료까지 대기

5. lock.lock()
   - 락 획득까지 대기

1.5 블로킹의 특징

블로킹의 특징:

장점:
  - 단순, 직관적
  - 코드 흐름 명확
  - 결과 바로 사용

단점:
  - 스레드 대기 (자원 낭비)
  - 많은 동시 요청 시 많은 스레드
  - 처리량 한계

1.6 ILIC 의 블로킹

@Service
public class ShipmentBlockingService {
    
    // 블로킹 — 전통적 처리
    public Shipment fetch(Long id) {
        // DB 조회 (블록)
        Shipment shipment = repository.findById(id);
        // 이 줄에서 DB 응답까지 대기 (제어권 잃음)
        
        // 외부 API (블록)
        Tracking tracking = trackingApi.fetch(shipment.getBlNo());
        // API 응답까지 대기
        
        shipment.setTracking(tracking);
        return shipment;
    }
    // 각 호출이 블로킹
    // 스레드가 대기
}

1.7 자기 점검 답변

블로킹의 정의는?

:
1. 정의:

  • 완료까지 제어권 잃고 대기
  • 호출자 멈춤
  1. 흐름:

    • 호출 → 제어권 넘김 → 대기 → 완료 → 복귀
  2. :

    • 전통 I/O
    • 동기 DB
    • Future.get
    • lock.lock
  3. 특징:

    • 단순
    • 스레드 대기 (낭비)

2️⃣ 논블로킹의 정의

2.1 논블로킹의 정의

논블로킹 (Non-Blocking):

  호출 즉시 제어권을 반환하여
  호출자가 다른 일을 할 수 있는 것.

핵심:
  - 제어권 즉시 반환
  - 호출자 대기 X
  - 다른 작업 가능

2.2 논블로킹의 흐름

논블로킹 흐름:

호출자 스레드:
  1. 함수 호출
  2. 즉시 제어권 반환 (결과 미완성일 수도)
  3. 다른 작업 진행
  4. 나중에 결과 확인 (polling) 또는 통보 (callback)

대기 X = 논블록

2.3 코드 예시

// 논블로킹 호출
public void nonBlockingExample() {
    System.out.println("Before");
    
    // 즉시 반환 (결과 미완성 가능)
    int bytesRead = nonBlockingRead();   // 데이터 없으면 0 반환 (대기 X)
    
    if (bytesRead == 0) {
        // 데이터 아직 없음, 다른 일
        doOtherWork();
    } else {
        // 데이터 있음
        process();
    }
    
    System.out.println("After");   // 즉시 진행
}

private int nonBlockingRead() {
    // NIO 채널 (논블로킹 모드)
    // 데이터 있으면 읽고, 없으면 0 반환 (대기 X)
    return channel.read(buffer);
}

2.4 논블로킹의 예

논블로킹 작업의 예:

1. NIO 채널 (논블로킹 모드)
   - channel.configureBlocking(false)
   - read() 가 즉시 반환 (0 가능)

2. CompletableFuture
   - 즉시 Future 반환
   - 콜백

3. tryLock()
   - 락 시도, 실패 시 즉시 false

4. Future.isDone()
   - 완료 확인 (대기 X)

5. Reactive (Flux/Mono)
   - 논블로킹 스트림

2.5 논블로킹의 특징

논블로킹의 특징:

장점:
  - 스레드 효율 (대기 X)
  - 적은 스레드로 많은 작업
  - 처리량 ↑
  - 응답성 ↑

단점:
  - 복잡 (polling/callback)
  - 결과 확인 로직 필요
  - 디버깅 어려움

2.6 ILIC 의 논블로킹

@Service
public class ShipmentNonBlockingService {
    
    // 논블로킹 — CompletableFuture
    public CompletableFuture<Shipment> fetchAsync(Long id) {
        return CompletableFuture
            .supplyAsync(() -> repository.findById(id))   // 백그라운드
            .thenCompose(shipment ->                       // 콜백 체인
                fetchTrackingAsync(shipment.getBlNo())
                    .thenApply(tracking -> {
                        shipment.setTracking(tracking);
                        return shipment;
                    }));
        // 호출자는 즉시 Future 반환받음 (대기 X)
    }
    
    // Reactive (WebFlux)
    public Mono<Shipment> fetchReactive(Long id) {
        return reactiveRepository.findById(id)   // 논블로킹
            .flatMap(shipment -> 
                trackingService.fetch(shipment.getBlNo())
                    .map(tracking -> {
                        shipment.setTracking(tracking);
                        return shipment;
                    }));
        // 완전 논블로킹 체인
    }
}

2.7 자기 점검 답변

논블로킹의 정의는?

:
1. 정의:

  • 즉시 제어권 반환
  • 호출자 대기 X
  1. 흐름:

    • 호출 → 즉시 반환 → 다른 작업 → 나중 확인/통보
  2. :

    • NIO 채널
    • CompletableFuture
    • tryLock
    • Reactive
  3. 특징:

    • 스레드 효율
    • 처리량 ↑
    • 복잡

3️⃣ 제어권의 의미

3.1 제어권이란

제어권 (Control):

  함수의 실행 흐름을 제어할 수 있는 권리.
  "지금 무엇을 실행할지 결정하는 권한."

스레드 관점:
  - 제어권 있음 = 코드 실행 중
  - 제어권 없음 = 다른 곳에 넘김 (대기)

3.2 제어권의 이동

제어권의 이동:

블로킹 호출:
  호출자 (제어권 있음)
    ↓ 함수 호출
  함수 (제어권 가져감)
    ↓ 실행 + 완료
  호출자 (제어권 돌아옴)

  → 함수 실행 중 호출자는 대기

논블로킹 호출:
  호출자 (제어권 있음)
    ↓ 함수 호출
  함수 (즉시 반환)
    ↓
  호출자 (제어권 유지)

  → 호출자가 계속 실행

3.3 "할 일이 없는 상태"

제어권 잃음 = 할 일 없음:

블로킹 시:
  - 제어권을 함수에 넘김
  - 호출자 스레드는 "할 일 없음"
  - = 블로킹 상태
  - CPU 사용 X (대기)

핵심:
  - 제어권 넘어감 → 대기
  - 그 스레드는 놀고 있음 (블록)

3.4 시각화

제어권의 흐름:

블로킹:
호출자: ━━━┓
            ┃ (제어권 넘김, 대기)
함수:       ┣━━━━━━┫ (실행)
            ┃ (제어권 반환)
호출자: ━━━┛━━━━ (계속)
        ↑대기 구간↑

논블로킹:
호출자: ━━━┓     ┏━━━━━━ (계속, 대기 X)
            ┃ 호출 ┃
함수:       ┗━━━┛ (즉시 반환)
        호출자 제어권 유지

3.5 제어권과 CPU

제어권과 CPU 사용:

블로킹 (제어권 넘김):
  - 호출자 스레드 대기
  - CPU 사용 X (보통 — 진짜 블로킹)
  - OS 가 다른 스레드에 CPU
  - 컨텍스트 스위칭

논블로킹 (제어권 유지):
  - 호출자 스레드 계속
  - CPU 계속 사용
  - 다른 작업 진행

3.6 ILIC 의 맥락

public class ControlExample {
    
    // 블로킹 — 제어권 넘김
    public void blocking() {
        log.info("Start");
        Shipment s = repository.findById(1L);   // 제어권 넘김 (대기)
        // 이 스레드는 DB 응답까지 할 일 없음
        log.info("Got: " + s);   // 제어권 돌아온 후
    }
    
    // 논블로킹 — 제어권 유지
    public void nonBlocking() {
        log.info("Start");
        CompletableFuture<Shipment> future = 
            CompletableFuture.supplyAsync(() -> repository.findById(1L));
        // 즉시 반환 (제어권 유지)
        
        log.info("Doing other work");   // 바로 실행
        doOtherWork();   // DB 조회 중에도 진행
        
        future.thenAccept(s -> log.info("Got: " + s));   // 완료 시
    }
}

3.7 자기 점검 답변

제어권의 의미는?

:
1. 정의:

  • 실행 흐름 제어 권리
  • 무엇을 실행할지 결정
  1. 이동:

    • 블로킹: 함수에 넘김 (대기)
    • 논블로킹: 유지 (계속)
  2. 할 일 없음:

    • 제어권 넘김 → 대기
    • 블로킹 상태
  3. CPU:

    • 블로킹: 대기 (CPU X)
    • 논블로킹: 계속 (CPU 사용)

4️⃣ "제어권을 가져갔는가?"

4.1 핵심 질문

Blocking/Non-Blocking 판단:

  "OS (또는 호출된 함수) 가
   제어권을 가져갔는가,
   즉시 돌려줬는가?"

  가져감 (대기) → 블로킹
  즉시 돌려줌 → 논블로킹

4.2 판단 흐름

판단:

함수 호출 시:
  1. 결과 미완성이면?
     - 대기 (제어권 가져감) → 블로킹
     - 즉시 반환 (제어권 돌려줌) → 논블로킹

블로킹:
  - 완료까지 안 돌려줌

논블로킹:
  - 완료 안 됐어도 즉시 돌려줌

4.3 예시 비교

// 블로킹 — 제어권 가져감
int data = inputStream.read();
// 데이터 없으면 올 때까지 대기 (제어권 안 돌려줌)

// 논블로킹 — 즉시 돌려줌
channel.configureBlocking(false);
int bytesRead = channel.read(buffer);
// 데이터 없으면 0 반환 (제어권 즉시 돌려줌)
if (bytesRead == 0) {
    // 데이터 없음, 다른 일
}

4.4 OS 레벨의 제어권

OS 레벨:

블로킹 시스템 콜:
  - read() 시스템 콜
  - 데이터 없으면 프로세스/스레드 대기 큐로
  - 데이터 오면 깨움
  - OS 가 제어권 관리

논블로킹 시스템 콜:
  - read() with O_NONBLOCK
  - 데이터 없으면 즉시 EWOULDBLOCK
  - 호출자 계속

4.5 시각화

제어권 가져감 여부:

블로킹 read():
  호출 → [OS: 데이터 대기...] → 데이터 → 반환
              ↑
         제어권 OS 가 가져감 (스레드 대기)

논블로킹 read():
  호출 → [OS: 데이터 확인] → 즉시 반환 (0 또는 데이터)
              ↑
         제어권 즉시 돌려줌

4.6 ILIC 의 맥락

public class ControlTransferExample {
    
    // 블로킹 소켓 (제어권 가져감)
    public byte[] readBlocking(Socket socket) throws IOException {
        InputStream in = socket.getInputStream();
        byte[] buffer = new byte[1024];
        int read = in.read(buffer);   // 데이터까지 대기 (제어권 가져감)
        return Arrays.copyOf(buffer, read);
    }
    
    // 논블로킹 채널 (제어권 즉시)
    public void readNonBlocking(SocketChannel channel) throws IOException {
        channel.configureBlocking(false);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        
        int read = channel.read(buffer);   // 즉시 반환 (제어권 유지)
        if (read == 0) {
            // 데이터 없음, 다른 일
            doOtherWork();
        } else if (read > 0) {
            // 데이터 처리
            process(buffer);
        }
    }
}

4.7 자기 점검 답변

"제어권을 가져갔는가?" 의 의미는?

:
1. 핵심 질문:

  • 제어권 가져감 vs 즉시 돌려줌
  • 가져감: 블로킹, 돌려줌: 논블로킹
  1. 판단:

    • 결과 미완성 시
    • 대기 → 블로킹
    • 즉시 반환 → 논블로킹
  2. OS 레벨:

    • 블로킹 시스템 콜 (대기 큐)
    • 논블로킹 (EWOULDBLOCK)
  3. :

    • read() 블로킹
    • channel.read() 논블로킹 (0 반환)

5️⃣ Sync와 Blocking의 차이

5.1 한 문장 정리

Sync vs Blocking 한 문장:

  Sync (동기) 는 "작업 완료를 확인하고 다음으로 가는가" (작업 순서) 이고,
  Blocking (블로킹) 은 "호출 시 제어권을 잃고 대기하는가" (제어권) 이다.

  Sync = 결과를 언제 받나
  Blocking = 호출 시 대기하나

5.2 다른 축

두 축:

Sync/Async:
  관심: 작업 완료 통보
  - 동기: 완료 확인 후 진행
  - 비동기: 완료 안 기다림, 통보

Blocking/Non-Blocking:
  관심: 제어권
  - 블로킹: 제어권 잃고 대기
  - 논블로킹: 제어권 유지

핵심:
  - 작업 순서 vs 제어권
  - 독립적 축

5.3 흔한 오해

오해:
  "동기 = 블로킹"
  "비동기 = 논블로킹"

진실:
  - 보통 함께 가지만
  - 항상 X
  - 4가지 조합 가능

예:
  - 동기 + 논블로킹 (polling)
  - 비동기 + 블로킹 (Future.get)

5.4 동기 + 논블로킹

// 동기 + 논블로킹 (polling)
public String syncNonBlocking() {
    // 논블로킹 (제어권 즉시)
    while (true) {
        int data = channel.read(buffer);   // 논블로킹 (즉시 반환)
        if (data > 0) {
            return process(buffer);   // 동기 (결과 확인 후 반환)
        }
        // 데이터 없으면 다시 시도 (polling)
        // 제어권 유지 (논블로킹)
        // 하지만 결과 확인하며 진행 (동기적)
    }
}
// 논블로킹 (제어권 유지) + 동기 (결과 확인)

5.5 비동기 + 블로킹

// 비동기 + 블로킹 (Future.get)
public Shipment asyncBlocking() throws Exception {
    // 비동기 (작업 백그라운드)
    Future<Shipment> future = executor.submit(() -> repository.findById(1L));
    
    // 블로킹 (결과 받을 때 대기)
    return future.get();   // ★ 여기서 블록 (제어권 잃음)
}
// 작업은 비동기, 결과 회수는 블로킹

5.6 비교 표

구분Sync/AsyncBlocking/Non-Blocking
작업 순서/통보제어권
질문완료 확인?대기하나?
동기/블로킹완료 확인 후대기
비동기/논블로킹통보즉시 반환

5.7 ILIC 의 맥락

public class SyncBlockingDistinction {
    
    // 동기 + 블로킹 (전통)
    public Shipment syncBlocking(Long id) {
        return repository.findById(id);
        // 결과 확인 (동기) + 대기 (블로킹)
    }
    
    // 동기 + 논블로킹 (polling)
    public Shipment syncNonBlocking(Future<Shipment> future) throws Exception {
        while (!future.isDone()) {   // 논블로킹 확인 (제어권 유지)
            doOtherWork();   // 다른 일
        }
        return future.get();   // 완료됨 (동기적 결과)
    }
    
    // 비동기 + 블로킹 (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));
        // 비동기 + 콜백 (논블로킹)
    }
}

5.8 자기 점검 답변

Sync와 Blocking의 차이는?

:
1. 한 문장:

  • Sync: 완료 확인 후 진행 (작업 순서)
  • Blocking: 제어권 잃고 대기 (제어권)
  1. 다른 축:

    • Sync/Async: 통보
    • Blocking/Non-Blocking: 제어권
  2. 오해:

    • 동기 = 블로킹 (X)
    • 4가지 조합
  3. 조합 예:

    • 동기 + 논블로킹 (polling)
    • 비동기 + 블로킹 (Future.get)

6️⃣ 논블로킹에서 결과 받기 (polling/callback)

6.1 두 가지 방법

논블로킹 결과 받기:

1. Polling (폴링)
   - 주기적으로 완료 확인
   - "다 됐니?" 반복

2. Callback (콜백)
   - 완료 시 통보
   - "다 되면 알려줘"

6.2 Polling

// Polling — 반복 확인
public Shipment pollingResult() throws Exception {
    Future<Shipment> future = executor.submit(() -> repository.findById(1L));
    
    while (!future.isDone()) {   // 완료 확인 (논블로킹)
        // 아직 안 됨
        doSomethingElse();   // 다른 일
        Thread.sleep(10);    // 잠시 후 다시
    }
    
    return future.get();   // 완료됨
}

// 특징:
// - 제어권 유지 (논블로킹)
// - 반복 확인 (CPU 사용)
// - 확인 간격 조절 필요

6.3 Polling 의 문제

Polling 의 트레이드오프:

너무 자주 확인:
  - CPU 낭비 (바쁜 대기)
  - busy waiting

너무 가끔 확인:
  - 응답 지연
  - 완료 후 늦게 인지

해결:
  - 적절한 간격
  - 또는 콜백 사용

6.4 Callback

// Callback — 완료 통보
public void callbackResult() {
    CompletableFuture
        .supplyAsync(() -> repository.findById(1L))
        .thenAccept(shipment -> {   // 완료 시 자동 호출
            process(shipment);
        });
    
    // 호출자는 확인 안 함
    // 완료 시 callback 자동
    doOtherWork();
}

// 특징:
// - 제어권 유지 (논블로킹)
// - 반복 확인 X (효율)
// - 완료 시 통보

6.5 Polling vs Callback

항목PollingCallback
확인반복 (능동)통보 (수동)
CPU사용 (확인)효율
복잡도단순콜백 등록
응답성간격에 의존즉시
권장드물게일반적

6.6 NIO Selector (고급)

// NIO Selector — 여러 채널 효율적 polling
public void selectorExample() throws IOException {
    Selector selector = Selector.open();
    channel.configureBlocking(false);
    channel.register(selector, SelectionKey.OP_READ);
    
    while (true) {
        selector.select();   // 준비된 채널까지 대기 (효율적)
        // 여러 채널 중 준비된 것만
        
        Set<SelectionKey> keys = selector.selectedKeys();
        for (SelectionKey key : keys) {
            if (key.isReadable()) {
                // 읽기 가능한 채널 처리
                read((SocketChannel) key.channel());
            }
        }
        keys.clear();
    }
}
// 1 스레드로 여러 채널 (이벤트 기반)
// busy polling 회피

6.7 ILIC 의 맥락

public class NonBlockingResult {
    
    // Polling (드물게 사용)
    public Shipment pollResult(Future<Shipment> future) throws Exception {
        int attempts = 0;
        while (!future.isDone() && attempts < 100) {
            Thread.sleep(50);   // 적절한 간격
            attempts++;
        }
        return future.isDone() ? future.get() : null;
    }
    
    // Callback (권장)
    public void processWithCallback(Long id, Consumer<Shipment> onComplete) {
        CompletableFuture
            .supplyAsync(() -> repository.findById(id))
            .thenAccept(onComplete)
            .exceptionally(error -> {
                log.error("Failed", error);
                return null;
            });
    }
    
    // 체이닝 (콜백의 발전)
    public CompletableFuture<EnrichedShipment> enrichChain(Long id) {
        return CompletableFuture
            .supplyAsync(() -> repository.findById(id))
            .thenCompose(this::fetchTrackingAsync)
            .thenApply(this::buildEnriched);
    }
}

6.8 자기 점검 답변

논블로킹에서 결과 받기는?

:
1. Polling:

  • 반복 확인 (isDone)
  • 능동
  • CPU 사용 (간격 조절)
  1. Callback:

    • 완료 통보 (thenAccept)
    • 수동
    • 효율
  2. 비교:

    • Polling: 단순, CPU 사용
    • Callback: 효율, 권장
  3. 고급:

    • NIO Selector (여러 채널)
    • 이벤트 기반

7️⃣ 블로킹의 스레드 상태

7.1 블로킹 시 상태

블로킹 시 스레드 상태:

I/O 블로킹:
  - RUNNABLE (자바 관점)
  - 또는 네이티브 대기

락 블로킹:
  - BLOCKED (synchronized)

wait/join:
  - WAITING

sleep/타임아웃:
  - TIMED_WAITING

핵심:
  - 블로킹 = CPU 안 씀 (대기)
  - 상태는 원인에 따라

7.2 자바 스레드 상태와 블로킹

자바 스레드 상태 (Phase 3 에서 정밀):

RUNNABLE:
  - 실행 또는 실행 가능
  - I/O 대기도 RUNNABLE (자바 관점)

BLOCKED:
  - synchronized 락 대기

WAITING:
  - wait(), join() (무한)

TIMED_WAITING:
  - sleep(ms), wait(ms)

블로킹 작업:
  - I/O: RUNNABLE (네이티브 대기)
  - 락: BLOCKED

7.3 I/O 블로킹의 특이점

I/O 블로킹은 RUNNABLE?:

자바 관점:
  - I/O 블로킹 중에도 RUNNABLE
  - 자바는 OS 대기를 모름

OS 관점:
  - 실제론 대기 (CPU X)
  - 대기 큐

이유:
  - 자바 스레드 상태는 JVM 레벨
  - OS 의 I/O 대기는 별개

→ jstack 에서 RUNNABLE 이지만
   실제론 I/O 대기일 수 있음

7.4 시각화

블로킹 작업별 상태:

synchronized 대기:
  스레드 → BLOCKED
  (락 획득까지)

I/O read():
  스레드 → RUNNABLE (자바)
         → 실제 OS 대기

Future.get():
  스레드 → WAITING

sleep(100):
  스레드 → TIMED_WAITING

7.5 블로킹과 스레드 풀

블로킹의 영향 — 스레드 풀:

블로킹 작업이 많으면:
  - 스레드가 대기로 점유
  - 풀 고갈
  - 새 작업 대기

예:
  - 풀 크기 10
  - 10개 작업이 모두 블로킹 I/O
  - 11번째 작업 대기

해결:
  - 풀 크기 ↑
  - 논블로킹
  - Virtual Thread

7.6 ILIC 의 맥락

public class BlockingThreadState {
    
    // 블로킹 작업이 스레드 점유
    public void demonstrateBlocking() {
        ExecutorService pool = Executors.newFixedThreadPool(10);
        
        for (int i = 0; i < 10; i++) {
            pool.submit(() -> {
                // 블로킹 I/O (스레드 점유)
                Shipment s = repository.findById(1L);   // DB 대기
                Tracking t = trackingApi.fetch(s.getBlNo());   // API 대기
                // 이 스레드는 대기 중 점유
            });
        }
        // 10 스레드 모두 블로킹 점유
        // 11번째 작업은 대기
        
        // 해결: Virtual Thread (Java 21+)
        ExecutorService virtual = Executors.newVirtualThreadPerTaskExecutor();
        // I/O 블로킹 시 OS 스레드 양보
        // 수만 작업 가능
    }
}

7.7 자기 점검 답변

블로킹의 스레드 상태는?

:
1. 상태:

  • I/O: RUNNABLE (자바), OS 대기
  • 락: BLOCKED
  • wait/join: WAITING
  • sleep: TIMED_WAITING
  1. I/O 특이점:

    • 자바는 RUNNABLE
    • 실제론 OS 대기
  2. 스레드 풀 영향:

    • 블로킹이 스레드 점유
    • 풀 고갈 가능
  3. 해결:

    • 논블로킹
    • Virtual Thread

8️⃣ 논블로킹 I/O의 동작

8.1 논블로킹 I/O

논블로킹 I/O (NIO):

  Java NIO (New I/O, Java 1.4+):
  - Channel + Buffer + Selector
  - 논블로킹 모드 지원

전통 I/O (블로킹):
  - Stream
  - read() 가 대기

NIO (논블로킹):
  - Channel.configureBlocking(false)
  - read() 즉시 반환

8.2 Channel 의 논블로킹 모드

// 논블로킹 채널
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);   // 논블로킹 모드

ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = channel.read(buffer);

// read 값:
// > 0: 읽은 바이트 수
// 0: 데이터 없음 (대기 X, 즉시 반환)
// -1: 연결 종료

if (read == 0) {
    // 데이터 없음, 다른 일
}

8.3 Selector (멀티플렉싱)

// Selector — 1 스레드로 여러 채널
public void selectorPattern() throws IOException {
    Selector selector = Selector.open();
    
    // 여러 채널 등록
    channel1.register(selector, SelectionKey.OP_READ);
    channel2.register(selector, SelectionKey.OP_READ);
    channel3.register(selector, SelectionKey.OP_READ);
    
    while (true) {
        selector.select();   // 준비된 채널까지 (효율적 대기)
        
        Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
        while (keys.hasNext()) {
            SelectionKey key = keys.next();
            keys.remove();
            
            if (key.isReadable()) {
                // 준비된 채널만 처리
                read((SocketChannel) key.channel());
            }
        }
    }
}
// 1 스레드 + 여러 채널 = 효율

8.4 논블로킹 I/O 의 효과

논블로킹 I/O 의 효과:

블로킹 I/O:
  - 연결 1개 = 스레드 1개
  - 1만 연결 = 1만 스레드
  - 자원 폭발

논블로킹 I/O (Selector):
  - 1 스레드로 여러 연결
  - 1만 연결 = 적은 스레드
  - C10K 문제 해결

예:
  - Netty, Nginx
  - 이벤트 루프

8.5 이벤트 루프

이벤트 루프 (Event Loop):

  논블로킹 I/O 의 핵심 패턴.

동작:
  while (true) {
      events = selector.select();   // 이벤트 대기
      for (event : events) {
          handle(event);   // 처리
      }
  }

  - 1 스레드 (또는 적은 수)
  - 이벤트 기반
  - Netty, Node.js, Vert.x

8.6 ILIC 의 맥락

// Spring WebFlux (논블로킹)
@RestController
public class ShipmentReactiveController {
    
    private final ShipmentReactiveService service;
    
    // 논블로킹 엔드포인트
    @GetMapping("/api/shipments/{id}")
    public Mono<Shipment> get(@PathVariable Long id) {
        return service.findById(id);   // 논블로킹
        // 적은 스레드로 많은 요청
    }
    
    @GetMapping("/api/shipments")
    public Flux<Shipment> getAll() {
        return service.findAll();   // 논블로킹 스트림
    }
}

@Service
public class ShipmentReactiveService {
    
    private final R2dbcEntityTemplate template;   // 논블로킹 DB
    
    public Mono<Shipment> findById(Long id) {
        return template.select(Shipment.class)
            .matching(query(where("id").is(id)))
            .one();
        // 완전 논블로킹 (R2DBC)
    }
}

8.7 자기 점검 답변

논블로킹 I/O 의 동작은?

:
1. NIO:

  • Channel + Buffer + Selector
  • 논블로킹 모드
  1. Channel:

    • configureBlocking(false)
    • read() 즉시 반환 (0 가능)
  2. Selector:

    • 1 스레드로 여러 채널
    • 멀티플렉싱
  3. 효과:

    • 적은 스레드로 많은 연결
    • C10K 해결
    • 이벤트 루프 (Netty)

9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
블로킹?완료까지 제어권 잃고 대기
논블로킹?즉시 제어권 반환
제어권?실행 흐름 제어 권리
판단 기준?제어권 가져갔나?
Sync vs Blocking?작업 순서 vs 제어권
논블로킹 결과?polling, callback
Polling?반복 확인
Callback?완료 통보
블로킹 스레드 상태?I/O는 RUNNABLE(자바)
논블로킹 I/O?NIO, Selector
Selector?1 스레드 여러 채널

9.2 자기 점검 체크리스트

블로킹

  • 정의 (제어권 잃고 대기)
  • 예 (I/O, lock)
  • 특징

논블로킹

  • 정의 (즉시 반환)
  • 예 (NIO, CompletableFuture)
  • 특징

제어권

  • 의미
  • 이동
  • CPU 관계

Sync vs Blocking

  • 다른 축
  • 4가지 조합

결과 받기

  • Polling
  • Callback
  • Selector

논블로킹 I/O

  • NIO
  • Selector
  • 이벤트 루프

9.3 추가 심화 질문

Q1: epoll, kqueue?

답:

  • Linux: epoll
  • macOS/BSD: kqueue
  • Windows: IOCP
  • OS 의 이벤트 알림 메커니즘
  • Selector 가 내부적으로 사용
  • 효율적 I/O 멀티플렉싱

Q2: C10K 문제?

답:

  • 1만 동시 연결 처리 문제
  • 블로킹: 1만 스레드 (불가능)
  • 논블로킹 + 이벤트 루프: 해결
  • Nginx, Netty

Q3: busy waiting?

답:

  • 반복 확인하며 CPU 소모
  • while(!done) {} (나쁨)
  • 해결: sleep, yield, 또는 콜백
  • Selector 가 효율적 대기

Q4: 블로킹이 항상 나쁜가?

답:

  • 아니다
  • 단순함 (가독성)
  • 적은 동시성이면 OK
  • Virtual Thread 가 블로킹 코드 + 효율
  • 상황에 따라

Q5: Reactive vs Virtual Thread?

답:

  • Reactive: 논블로킹, 선언적, 복잡
  • Virtual Thread: 블로킹 코드, 간단, Java 21+
  • Virtual Thread 가 더 쉬움
  • 둘 다 높은 동시성

🎯 핵심 요약 — 3줄 정리

1. Blocking vs Non-Blocking

  • 블로킹: 완료까지 제어권 잃고 대기
  • 논블로킹: 즉시 제어권 반환

2. 제어권 + Sync 구분

  • 판단: 제어권 가져갔나?
  • Sync(작업 순서) ≠ Blocking(제어권)

3. 결과 받기

  • Polling (반복 확인)
  • Callback (완료 통보)
  • NIO Selector (1 스레드 여러 채널)

📚 다음으로...

Unit 2.3 — 4분면 매트릭스 (★ 마스터, 면접 단골)

이번 Unit에서 제어권 축을 봤다면, 다음은 두 축의 4가지 조합.

  • Sync + Blocking (전통 IO)
  • Sync + Non-Blocking (NIO Polling)
  • Async + Blocking (Future.get)
  • Async + Non-Blocking (CompletableFuture)
  • 실무 권장

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분면 매트릭스 (2/3 진행)

총: 6/35 Unit
profile
Software Developer

0개의 댓글