이번 글은 Java 21의 핵심 기능이자 최근 면접에서 가장 많이 나오는 주제이다.
많은 사람들이
"Virtual Thread는 엄청 가벼운 Thread다."
정도로만 알고 있다.
하지만 그것만으로는 왜 만들어졌는지 이해할 수 없다.
Virtual Thread를 이해하려면 먼저
기존 Thread가 왜 비싼지
부터 이해해야 한다.
Virtual Thread(Java 21)
OS Thread를 효율적으로 공유하여 수십만 개의 동시 작업을 처리할 수 있도록 만든 JVM 수준의 경량 Thread
(Java 21 LTS 정식 지원)
먼저 질문 하나
웹 서버에서
사용자 1만 명
↓
동시에 API 요청
이 들어오면 어떻게 될까?
많은 사람들이
요청 1개
↓
Thread 1개
↓
끝
이라고 생각한다.
실제로도 기존 Java는 그렇게 동작했다.
Platform Thread
기존 Java Thread
new Thread(() -> {
service.execute();
}).start();
메모리 구조
Java Thread
↓
OS Thread
↓
CPU 실행
즉,
Java Thread 하나가
OS Thread 하나를 독점한다.
왜 비쌀까?
OS Thread는 운영체제가 직접 관리한다.
생성 과정
new Thread()
↓
OS Kernel 호출
↓
Stack 생성
↓
Thread 생성
↓
Scheduler 등록
이 과정은 비용이 매우 크다.
Stack도 메모리를 사용한다.
Thread 하나당
보통
Stack
1MB 정도
를 가진다.
1000개 Thread
1000 × 1MB
↓
약 1GB
이다.
100000개라면?
100000 × 1MB
↓
100GB
현실적으로 불가능하다.
그런데 대부분의 Thread는 무엇을 하고 있을까?
예를 들어
String result =
httpClient.call();
실행
Thread
↓
HTTP 요청
↓
응답 대기
↓
...
↓
...
↓
응답
↓
다음 코드 실행
문제는
응답 대기
↓
Thread는 아무 일도 안 함
↓
OS Thread는 계속 점유
한다는 것이다.
그림으로 보면
Platform Thread
──────────────────────
실행
대기
대기
대기
실행
대기
──────────────────────
CPU는 쉬고 있는데
Thread는 계속 메모리를 차지한다.
Virtual Thread 등장
Java는 생각했다.
대기하는 동안 OS Thread를 반납하면 되지 않을까?
Virtual Thread
Virtual Thread
↓
JVM Scheduler
↓
OS Thread(Carrier Thread)
↓
CPU
여기서 중요한 것은
Virtual Thread는
OS Thread를 직접 가지지 않는다는 것이다.
Carrier Thread
Virtual Thread는
실행할 때만
Carrier Thread(OS Thread)를 빌린다.
Virtual Thread 1
↓
Carrier Thread
↓
실행
↓
대기(IO)
↓
Carrier 반환
응답이 오면
Virtual Thread
↓
다른 Carrier Thread
↓
다시 실행
된다.
그림으로 이해
기존
Thread1 ───── OS1
Thread2 ───── OS2
Thread3 ───── OS3
Virtual Thread
Virtual1
Virtual2
Virtual3
Virtual4
Virtual5
Virtual6
│
▼
JVM Scheduler
│
▼
OS Thread 1
OS Thread 2
OS Thread 3
수천 개의 Virtual Thread가
몇 개의 OS Thread를 공유한다.
생성 비용
Platform Thread
new Thread(...)
↓
OS 호출
↓
비쌈
Virtual Thread
Thread.startVirtualThread(...)
↓
JVM 객체 생성
↓
매우 저렴
코드도 거의 같다.
기존
Thread thread =
new Thread(() -> {
work();
});
thread.start();
Virtual Thread
Thread thread =
Thread.startVirtualThread(() -> {
work();
});
한 줄만 달라진다.
ExecutorService도 지원한다.
try (
ExecutorService executor =
Executors.newVirtualThreadPerTaskExecutor()
) {
executor.submit(() -> {
service.execute();
});
}
Task 하나당
Virtual Thread 하나를 생성한다.
왜 성능이 좋아질까?
예를 들어
10000개의 HTTP 요청
Platform Thread
10000 Thread
↓
10000 OS Thread 필요
↓
메모리 부족
Virtual Thread
10000 Virtual Thread
↓
50 Carrier Thread
↓
IO 대기 시 반납
↓
50개 OS Thread만 사용
엄청난 차이가 난다.
언제 가장 효과적일까?
IO 작업
DB 조회
HTTP 호출
Redis 조회
파일 읽기
API 호출
이런 작업은
대기 시간이 길다.
Virtual Thread가 가장 효과적이다.
언제 효과가 없을까?
CPU 연산
for (...) {
복잡한 계산
}
AI 계산
암호화
영상 처리
행렬 계산
이런 작업은
계속 CPU 사용
↓
Carrier Thread 반납 안 함
Platform Thread와 큰 차이가 없다.
Pinning이란?
Virtual Thread에도 주의할 점이 있다.
다음 코드를 보자.
synchronized(lock) {
Thread.sleep(1000);
}
Virtual Thread는
synchronized
↓
Lock 유지
↓
Carrier Thread 반납 불가
이다.
이것을
Pinning
이라고 한다.
즉,
Virtual Thread인데
Platform Thread처럼 동작한다.
ReentrantLock은?
lock.lock();
try {
Thread.sleep(1000);
}
finally {
lock.unlock();
}
ReentrantLock은
Virtual Thread 친화적으로 설계되어
Carrier Thread를 효율적으로 사용할 수 있다.
최근 코드에서는
synchronized
↓
ReentrantLock
을 권장하는 이유 중 하나이다.
Spring Boot에서는?
Spring Boot 3.2+
application.properties
spring.threads.virtual.enabled=true
끝이다.
Tomcat 요청 처리도
Virtual Thread를 사용할 수 있다.
@Async도 가능하다.
@Async
public void sendMail() {
}
Executor를
Executors.newVirtualThreadPerTaskExecutor()
로 설정하면
Virtual Thread 기반으로 동작한다.
ThreadLocal은 어떻게 될까?
많은 사람들이
Virtual Thread
↓
ThreadLocal 못 쓰는 거 아닌가?
라고 생각한다.
아니다.
Virtual Thread도
자신만의 ThreadLocalMap을 가진다.
기존 코드
ThreadLocal<User> holder
holder.set(user);
Virtual Thread에서도
동일하게 동작한다.
실무에서 사용할까?
최근 추세는
Spring Boot 3.x
↓
Java 21
↓
Virtual Thread 활성화
프로젝트가 점점 늘어나고 있다.
특히
REST API
MSA
Gateway
외부 API 호출
DB 조회
가 많은 서비스에서는
매우 큰 장점을 가진다.
Platform Thread vs Virtual Thread
| OS Thread와 1:1 | JVM이 OS Thread를 공유 |
| 생성 비용 큼 | 생성 비용 매우 작음 |
| 메모리 사용 큼 | 메모리 사용 적음 |
| 수천 개 생성 어려움 | 수십만 개 생성 가능 |
| IO 대기 시 Thread 점유 | IO 대기 시 Carrier 반환 |
Virtual Thread vs 비동기(CompletableFuture)
많은 사람들이
Virtual Thread
=
CompletableFuture
라고 생각한다.
사실은
전혀 다르다.
CompletableFuture
비동기 프로그래밍 모델
Virtual Thread
동기 코드를 유지하면서
Thread 비용을 줄이는 기술
이다.
면접 포인트
Virtual Thread란?
JVM이 OS Thread(Carrier Thread)를 효율적으로 공유하여 매우 많은 동시 작업을 처리할 수 있도록 만든 경량 Thread이다.
언제 가장 효과적인가?
- DB 조회
- HTTP 호출
- 파일 IO
- 네트워크 IO
처럼 대기 시간이 긴 IO 작업에서 가장 큰 효과를 낸다.
언제 효과가 적은가?
CPU 연산 중심 작업에서는 Carrier Thread를 계속 사용하므로 Platform Thread와 큰 차이가 없다.
Pinning이란?
synchronized 등으로 인해 Virtual Thread가 Carrier Thread를 반납하지 못하고 계속 점유하는 현상이다.
🎉 최신 Java(Java 17+) 챕터 완료
지금까지의 흐름은
var
↓
Text Block
↓
Switch Expression
↓
Pattern Matching for switch
↓
record
↓
sealed class
↓
Optional 심화
↓
Stream 개선사항
↓
Virtual Thread(Java 21)
이 순서가 자연스럽게 이어진다.
다음 챕터 추천
다음은 10. Spring과 연결되는 Java 핵심 개념이다.
Reflection과 Spring DI
Bean 생성 과정
객체 생명주기와 Spring Bean
Dynamic Proxy와 Spring AOP
JDK Proxy vs CGLIB in Spring
Java Proxy 기반 트랜잭션 처리
ThreadLocal과 Spring Security
ThreadLocal과 TransactionSynchronizationManager
ExecutorService와 @Async
Annotation 기반 동작 원리
Spring MVC 예외 처리 내부 구조
지금까지 공부한 Java 내부 동작이 실제 Spring Framework 안에서 어떻게 연결되는지 하나의 흐름으로 이해할 수 있는 챕터가 된다.
'language > java' 카테고리의 다른 글
| Bean 생성 과정 (0) | 2026.06.17 |
|---|---|
| Reflection과 Spring DI (0) | 2026.06.17 |
| Stream 개선사항(Java 9~21) (0) | 2026.06.17 |
| Optional 심화 (0) | 2026.06.17 |
| sealed class(Java 17) (0) | 2026.06.17 |
댓글