Java 21에서 도입된 Virtual Thread의 구조와 동작 원리를 이해하기 쉽게 정리한 문서입니다.
┌─────────────────────────────────────────────────────────────────┐
│ Platform Thread = 기존의 Java Thread │
├─────────────────────────────────────────────────────────────────┤
│ │
│ • OS 스레드와 1:1로 매핑되는 "무거운" 스레드 │
│ • 생성 비용: 약 1MB 스택 메모리 │
│ • OS 커널이 스케줄링 담당 │
│ • 컨텍스트 스위칭 = OS 레벨에서 발생 (비용 높음) │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Java Thread │ ──1:1── │ OS Thread │ │
│ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Virtual Thread = JVM이 관리하는 "경량" 스레드 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ • OS 스레드와 직접 매핑되지 않음 │
│ • 생성 비용: 수백 바이트 ~ 수 KB │
│ • JVM이 스케줄링 담당 │
│ • 컨텍스트 스위칭 = JVM 레벨에서 발생 (비용 낮음) │
│ │
│ ┌─────────────┐ │
│ │ Virtual │ ──┐ │
│ │ Thread 1 │ │ ┌─────────────┐ ┌─────────────┐ │
│ ├─────────────┤ ├──N:1─│ Platform │─1:1─│ OS Thread │ │
│ │ Virtual │ │ │ Thread │ └─────────────┘ │
│ │ Thread 2 │ ──┤ └─────────────┘ │
│ ├─────────────┤ │ (Carrier) │
│ │ Virtual │ │ │
│ │ Thread 3 │ ──┘ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Carrier Thread = Virtual Thread를 "태워서" 실행하는 스레드 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ • 실제로는 Platform Thread │
│ • Virtual Thread의 작업을 대신 실행해줌 │
│ • "캐리어(운반자)"라는 이름의 의미: │
│ - Virtual Thread를 "태워서" CPU까지 운반 │
│ - 실제 OS 스레드 위에서 실행되게 해줌 │
│ │
│ 비유: │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Virtual Thread = 승객 (가볍고 많을 수 있음) ││
│ │ Carrier Thread = 버스 (무겁지만 승객을 태워서 이동) ││
│ │ OS Thread = 도로 (실제 이동이 일어나는 곳) ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ ForkJoinPool = Virtual Thread들의 스케줄러 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 역할: │
│ • Carrier Thread Pool을 관리 │
│ • Virtual Thread 작업을 Carrier Thread에 분배 │
│ • Work Stealing 알고리즘으로 부하 분산 │
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ ForkJoinPool ││
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ││
│ │ │ Carrier │ │ Carrier │ │ Carrier │ ││
│ │ │ Thread 1 │ │ Thread 2 │ │ Thread 3 │ ││
│ │ │ [workQueue] │ │ [workQueue] │ │ [workQueue] │ ││
│ │ └──────────────┘ └──────────────┘ └──────────────┘ ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Work Queue = 각 Carrier Thread가 가진 작업 대기열 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ • 각 Carrier Thread마다 하나씩 보유 │
│ • Virtual Thread의 작업(runContinuation)들이 대기 │
│ • Deque(양방향 큐) 구조 │
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Carrier Thread의 Work Queue ││
│ │ ││
│ │ ┌────────┬────────┬────────┬────────┐ ││
│ │ │ Task 1 │ Task 2 │ Task 3 │ Task 4 │ ← push (새 작업) ││
│ │ └────────┴────────┴────────┴────────┘ ││
│ │ ↑ ││
│ │ pop (실행할 작업) ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ runContinuation = Virtual Thread의 "실제 작업 내용" │
├─────────────────────────────────────────────────────────────────┤
│ │
│ • Virtual Thread가 실행해야 할 코드 (Runnable) │
│ • 중단점(suspension point)에서 상태 저장 가능 │
│ • 재개 시 저장된 지점부터 계속 실행 │
│ │
│ 비유: │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ runContinuation = 책갈피가 있는 책 ││
│ │ ││
│ │ • 읽다가 중단 → 책갈피 끼움 (상태 저장) ││
│ │ • 나중에 재개 → 책갈피 위치부터 계속 (상태 복원) ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Work Stealing = 유휴 스레드가 바쁜 스레드의 작업을 가져옴 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 상황: Carrier-1은 바쁘고, Carrier-3은 할 일이 없음 │
│ │
│ Carrier-1: [Task1, Task2, Task3, Task4] ← 작업 많음 │
│ Carrier-2: [Task5, Task6] │
│ Carrier-3: [] ← 유휴 상태 │
│ │
│ Work Stealing 발생: │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Carrier-3이 Carrier-1의 큐 뒤쪽에서 Task4를 "훔쳐옴" ││
│ │ ││
│ │ Carrier-1: [Task1, Task2, Task3] ││
│ │ Carrier-3: [Task4] ← 훔쳐온 작업 실행 ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ 효과: 모든 Carrier Thread가 균등하게 일함 → 부하 분산 │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ park = 스레드를 일시 정지 (대기 상태로 전환) │
│ unpark = 스레드를 깨움 (실행 가능 상태로 전환) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Platform Thread의 park/unpark: │
│ • OS 커널에 요청 → OS가 스레드 스케줄링 │
│ • 비용 높음 (시스템 콜 필요) │
│ │
│ Virtual Thread의 park/unpark: │
│ • JVM 내부에서 처리 → OS 개입 없음 │
│ • 비용 낮음 (단순히 Work Queue에서 제거/추가) │
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ park (대기): ││
│ │ • Virtual Thread를 Carrier에서 분리 (unmount) ││
│ │ • 상태를 힙 메모리에 저장 ││
│ │ • Carrier Thread는 다른 Virtual Thread 실행 ││
│ │ ││
│ │ unpark (깨움): ││
│ │ • 힙 메모리에서 상태 복원 ││
│ │ • Work Queue에 다시 추가 ││
│ │ • Carrier Thread에 재할당 (mount) ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ mount = Virtual Thread가 Carrier Thread에 "탑승" │
│ unmount = Virtual Thread가 Carrier Thread에서 "하차" │
├─────────────────────────────────────────────────────────────────┤
│ │
│ mount (탑승): │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Virtual Thread가 Carrier Thread 위에서 실행 시작 ││
│ │ ││
│ │ ┌──────────────┐ ││
│ │ │ Virtual │ ──mount──▶ ┌──────────────┐ ││
│ │ │ Thread │ │ Carrier │ ││
│ │ │ (힙 메모리) │ │ Thread │ ││
│ │ └──────────────┘ │ (실행 중) │ ││
│ │ └──────────────┘ ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ unmount (하차): │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ I/O 대기 등으로 Virtual Thread가 Carrier에서 분리 ││
│ │ ││
│ │ ┌──────────────┐ ┌──────────────┐ ││
│ │ │ Virtual │ ◀──unmount─│ Carrier │ ││
│ │ │ Thread │ │ Thread │ ││
│ │ │ (힙에 저장) │ │ (다른 VT 실행)│ ││
│ │ └──────────────┘ └──────────────┘ ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Platform Thread의 한계 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 생성 비용이 높음 │
│ • 스레드당 약 1MB 스택 메모리 │
│ • 1000개 스레드 = 1GB 메모리 │
│ │
│ 2. OS 스레드와 1:1 매핑 │
│ • OS 스레드 수에 제한 있음 │
│ • 수천 개 이상 생성 어려움 │
│ │
│ 3. Context Switching 비용 높음 │
│ • OS 커널 개입 필요 (시스템 콜) │
│ • 레지스터 저장/복원, 캐시 무효화 │
│ │
│ 4. I/O 대기 시 비효율 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Platform Thread가 I/O 대기 시: │ │
│ │ • OS 스레드가 블로킹됨 │ │
│ │ • 아무것도 안 하면서 리소스 점유 │ │
│ │ • 다른 작업을 처리할 수 없음 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ VirtualThread 객체의 내부 구조 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ VirtualThread { │
│ │ │
│ ├── carrierThread ──────────────────────────────────────┐ │
│ │ • 실제 작업을 수행하는 Platform Thread │ │
│ │ • Virtual Thread가 mount될 대상 │ │
│ │ • workQueue를 보유 │ │
│ │ │ │
│ ├── scheduler (ForkJoinPool) ───────────────────────────┤ │
│ │ • Carrier Thread Pool 관리 │ │
│ │ • Virtual Thread 작업 스케줄링 │ │
│ │ • Work Stealing으로 부하 분산 │ │
│ │ │ │
│ └── runContinuation ────────────────────────────────────┘ │
│ • Virtual Thread의 실제 작업 내용 (Runnable) │
│ • 중단/재개 가능한 실행 단위 │
│ } │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Virtual Thread 아키텍처 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ JVM (User Space) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ ForkJoinPool (Scheduler) │ │ │
│ │ │ │ │ │
│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │
│ │ │ │ Carrier-1 │ │ Carrier-2 │ │ Carrier-3 │ │ │ │
│ │ │ │ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ │ │ │ │
│ │ │ │ │workQueue│ │ │ │workQueue│ │ │ │workQueue│ │ │ │ │
│ │ │ │ │[VT1,VT4]│ │ │ │[VT2,VT5]│ │ │ │[VT3] │ │ │ │ │
│ │ │ │ └─────────┘ │ │ └─────────┘ │ │ └─────────┘ │ │ │ │
│ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │
│ │ │ │ │ │ │ │ │
│ │ └─────────┼───────────────┼───────────────┼──────────┘ │ │
│ │ │ │ │ │ │
│ │ ┌─────────▼───────────────▼───────────────▼──────────┐ │ │
│ │ │ 힙 메모리 │ │ │
│ │ │ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ │ │
│ │ │ │VT1 │ │VT2 │ │VT3 │ │VT4 │ │VT5 │ ... (경량) │ │ │
│ │ │ └────┘ └────┘ └────┘ └────┘ └────┘ │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 1:1 매핑 │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ OS (Kernel Space) │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ OS Thread-1 │ │ OS Thread-2 │ │ OS Thread-3 │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Virtual Thread 실행 흐름 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ STEP 1: 작업 제출 (Submit) │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Virtual Thread 생성 시, runContinuation이 ││
│ │ Carrier Thread의 workQueue에 push됨 ││
│ │ ││
│ │ VirtualThread.start() ││
│ │ │ ││
│ │ ▼ ││
│ │ scheduler.submit(runContinuation) ││
│ │ │ ││
│ │ ▼ ││
│ │ carrierThread.workQueue.push(runContinuation) ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ STEP 2: 작업 실행 (Execute) │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ ForkJoinPool이 Work Stealing 방식으로 ││
│ │ Carrier Thread에 작업 분배 ││
│ │ ││
│ │ Carrier Thread: ││
│ │ 1. workQueue에서 runContinuation pop ││
│ │ 2. Virtual Thread를 mount (탑승) ││
│ │ 3. runContinuation 실행 ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ STEP 3: I/O 발생 시 park (일시정지) │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ I/O, Sleep, Lock 대기 시: ││
│ │ ││
│ │ 1. Virtual Thread가 park 호출 ││
│ │ 2. runContinuation 상태를 힙 메모리에 저장 ││
│ │ 3. workQueue에서 pop (제거) ││
│ │ 4. Carrier Thread에서 unmount (하차) ││
│ │ 5. Carrier Thread는 다른 Virtual Thread 실행 ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ STEP 4: I/O 완료 시 unpark (재개) │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ I/O 완료, Sleep 종료, Lock 획득 시: ││
│ │ ││
│ │ 1. Virtual Thread가 unpark 호출 ││
│ │ 2. scheduler가 runContinuation을 다시 스케줄링 ││
│ │ 3. 어떤 Carrier Thread의 workQueue에 push ││
│ │ 4. Carrier Thread에 mount되어 실행 재개 ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Virtual Thread 생명주기 타임라인 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 시간 → │
│ ════════════════════════════════════════════════════════════ │
│ │
│ Virtual Thread: │
│ ┌────────┐ ┌─────────────┐ ┌────────┐ │
│ │ 실행 │ │ WAITING │ │ 실행 │ │
│ │(mount) │ │ (unmount) │ │(mount) │ │
│ └───┬────┘ └──────┬──────┘ └───┬────┘ │
│ │ │ │ │
│ │ I/O 요청 │ I/O 완료 │ │
│ │ (park) │ (unpark) │ │
│ ▼ ▼ ▼ │
│ │
│ Carrier Thread: │
│ ┌────────┐ ┌──────────────────────────┐ ┌────────┐ │
│ │ VT-1 │ │ VT-2 (다른 Virtual) │ │ VT-1 │ │
│ │ 실행중 │ │ 실행중 │ │ 재개 │ │
│ └────────┘ └──────────────────────────┘ └────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 핵심: Carrier Thread는 놀지 않는다! ││
│ │ VT-1이 대기하는 동안 VT-2를 실행 ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
// VirtualThread.unpark() 내부 동작 (단순화)
void unpark() {
// 1. 현재 상태 확인
if (state == WAITING) {
// 2. 상태 변경: WAITING → RUNNABLE
state = RUNNABLE;
// 3. 스케줄러에 작업 제출
// → Carrier Thread의 workQueue에 추가됨
submitRunContinuation();
}
}
void submitRunContinuation() {
// ForkJoinPool에 작업 제출
scheduler.execute(runContinuation);
// → 내부적으로 어떤 Carrier Thread의 workQueue에 push됨
}
┌─────────────────────────────────────────────────────────────────┐
│ I/O 발생 시 Virtual Thread park 흐름 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 예: Socket 통신에서 응답 대기 │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 1. VirtualThread에서 socket.read() 호출 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 2. NIOSocketImpl.park() 호출 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 3. 현재 스레드가 Virtual Thread인지 확인 │ │
│ │ │ │ │
│ │ ┌───────────┴───────────┐ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ [Platform Thread] [Virtual Thread] │ │
│ │ Net.poll() 호출 Poller.poll() 호출 │ │
│ │ (OS 커널에 요청) (JVM 내부 처리) │ │
│ │ (비용 높음) (비용 낮음) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ Virtual Thread park │ │
│ │ • unmount from Carrier │ │
│ │ • 상태를 힙에 저장 │ │
│ │ • Carrier는 다른 VT 실행 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Thread.sleep()에서의 Virtual Thread 분기 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ // JDK 21 Thread.sleep() 내부 (단순화) │
│ │
│ public static void sleep(long millis) { │
│ if (currentThread() instanceof VirtualThread vt) { │
│ // Virtual Thread인 경우: │
│ // → JVM 내부에서 가볍게 park │
│ vt.sleepNanos(millis * 1_000_000L); │
│ } else { │
│ // Platform Thread인 경우: │
│ // → OS 커널에 요청 (기존 방식) │
│ sleep0(millis); // native method │
│ } │
│ } │
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Virtual Thread sleep: ││
│ │ • park → 힙에 저장 → Carrier 해제 ││
│ │ • 타이머 완료 시 → unpark → 재스케줄링 ││
│ │ • OS 개입 없음! ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ LockSupport.park() 변화 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ JDK 17 (기존): │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ public static void park() { ││
│ │ UNSAFE.park(false, 0L); // 항상 native 호출 ││
│ │ } ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ JDK 21 (Virtual Thread 지원): │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ public static void park() { ││
│ │ Thread t = Thread.currentThread(); ││
│ │ if (t.isVirtual()) { ││
│ │ VirtualThreads.park(); // JVM 내부 처리 ││
│ │ } else { ││
│ │ UNSAFE.park(false, 0L); // 기존 native 호출 ││
│ │ } ││
│ │ } ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 핵심: 기존 코드 수정 없이 Virtual Thread 지원! ││
│ │ Thread.sleep(), Lock, I/O 모두 자동으로 동작 ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Socket I/O에서의 변화 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ JDK 17: │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ private void park(FileDescriptor fd, int event) { ││
│ │ Net.poll(fd, event, -1); // 항상 커널 poll ││
│ │ } ││
│ │ ││
│ │ → OS 스레드가 블로킹됨 ││
│ │ → 다른 작업 처리 불가 ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ JDK 21: │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ private void park(FileDescriptor fd, int event) { ││
│ │ Thread t = Thread.currentThread(); ││
│ │ if (t.isVirtual()) { ││
│ │ Poller.poll(fd, event, ...); // JVM 내부 처리 ││
│ │ // → Virtual Thread만 park ││
│ │ // → Carrier Thread는 다른 VT 실행 ││
│ │ } else { ││
│ │ Net.poll(fd, event, -1); // 기존 방식 ││
│ │ } ││
│ │ } ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Virtual Thread 핵심 요약 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 구조 │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ VirtualThread ││
│ │ ├── carrierThread: 실제 실행을 담당하는 Platform Thread ││
│ │ ├── scheduler: ForkJoinPool (Carrier Thread Pool 관리) ││
│ │ └── runContinuation: 실행할 작업 (중단/재개 가능) ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ 2. 동작 원리 │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 시작: runContinuation → workQueue에 push ││
│ │ 실행: Carrier Thread가 workQueue에서 pop → 실행 ││
│ │ 대기: I/O 발생 → park → unmount → 힙에 저장 ││
│ │ 재개: I/O 완료 → unpark → workQueue에 push → 재실행 ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ 3. Platform Thread와의 차이 │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ │ Platform Thread │ Virtual Thread ││
│ │ ─────────┼────────────────────┼──────────────────────── ││
│ │ 매핑 │ OS 스레드 1:1 │ Carrier Thread N:1 ││
│ │ 생성비용 │ ~1MB │ 수백 바이트 ││
│ │ 스케줄링 │ OS 커널 │ JVM (ForkJoinPool) ││
│ │ I/O 대기 │ OS 스레드 블로킹 │ unmount (Carrier 해제) ││
│ │ CS 비용 │ 높음 (시스템 콜) │ 낮음 (JVM 내부) ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ 4. 호환성 │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ • 기존 Thread API 그대로 사용 가능 ││
│ │ • sleep(), Lock, I/O 모두 자동으로 Virtual Thread 지원 ││
│ │ • JDK가 내부적으로 Virtual Thread 분기 처리 ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ 5. 핵심 이점 │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ • I/O 대기 중 Carrier Thread가 다른 작업 처리 ││
│ │ • 수백만 개 Virtual Thread 생성 가능 ││
│ │ • 컨텍스트 스위칭 비용 대폭 감소 ││
│ │ • 기존 코드 수정 없이 성능 향상 ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ GPT API 호출에 Virtual Thread 적용 효과 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 기존 (Platform Thread): │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Thread-1: [요청] ─────────────────────────── [응답처리] ││
│ │ │← 1~2초 대기 (스레드 점유) →│ ││
│ │ ││
│ │ 문제: 대기 중에도 ~1MB 메모리 점유 ││
│ │ OS 스레드 낭비 ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ Virtual Thread 적용 후: │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ VT-1: [요청] ─park─▶ (힙에 저장, 수백 바이트) ││
│ │ │ ││
│ │ Carrier: ──────┼─▶ VT-2, VT-3, ... 실행 (놀지 않음) ││
│ │ │ ││
│ │ VT-1: ◀─unpark─ [응답처리] ││
│ │ ││
│ │ 효과: 대기 중 Carrier 해제 → 다른 작업 처리 ││
│ │ 수백만 개 동시 요청 가능 ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘