Contents
see List개요
Java 21에서 정식 도입된 Virtual Thread(가상 스레드)는 Java 동시성 프로그래밍의 패러다임을 완전히 바꾸고 있습니다. 기존 플랫폼 스레드(OS 스레드)의 무거운 비용 문제를 해결하며, 수백만 개의 동시 작업을 손쉽게 처리할 수 있게 합니다. 이 글에서는 Virtual Thread의 동작 원리와 실전 활용 패턴을 상세히 다룹니다.
핵심 개념
Virtual Thread는 JVM이 관리하는 경량 스레드로, OS 스레드 위에서 다중화(multiplexing)됩니다. 핵심 특성은 다음과 같습니다.
- 경량성: 플랫폼 스레드가 약 1MB 스택 메모리를 차지하는 반면, Virtual Thread는 수 KB에 불과
- 자동 스케줄링: ForkJoinPool 기반의 캐리어 스레드에서 자동으로 마운트/언마운트
- 블로킹 투명성: I/O 블로킹 시 캐리어 스레드를 자동으로 해제하여 다른 Virtual Thread가 사용
- 기존 API 호환: Thread, ExecutorService 등 기존 API를 그대로 사용 가능
- 구조적 동시성: StructuredTaskScope를 통한 안전한 동시 작업 관리
실전 예제
기본적인 Virtual Thread 생성과 ExecutorService 활용 예제입니다.
// 기본 Virtual Thread 생성
Thread vThread = Thread.ofVirtual()
.name("worker-", 0)
.start(() -> {
System.out.println("Virtual Thread: " + Thread.currentThread());
});
// ExecutorService와 함께 사용
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 100_000; i++) {
final int taskId = i;
futures.add(executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return "Task " + taskId + " 완료";
}));
}
// 10만 개의 동시 작업이 가상 스레드로 처리됨
for (Future<String> future : futures) {
future.get(); // 결과 수집
}
}
StructuredTaskScope를 활용한 구조적 동시성 예제입니다.
// 구조적 동시성: 여러 API를 병렬 호출
public record UserProfile(String name, List<Order> orders, int points) {}
public UserProfile fetchUserProfile(long userId) throws Exception {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// 세 API를 병렬로 호출
Subtask<String> nameTask = scope.fork(() -> fetchUserName(userId));
Subtask<List<Order>> ordersTask = scope.fork(() -> fetchOrders(userId));
Subtask<Integer> pointsTask = scope.fork(() -> fetchPoints(userId));
scope.join(); // 모든 작업 대기
scope.throwIfFailed(); // 하나라도 실패 시 예외
return new UserProfile(
nameTask.get(),
ordersTask.get(),
pointsTask.get()
);
}
}
// Spring Boot 3.2+에서 Virtual Thread 활성화
// application.yml
// spring:
// threads:
// virtual:
// enabled: true
활용 팁
- Virtual Thread는 I/O 바운드 작업에 최적화되어 있으며, CPU 바운드 작업에는 기존 플랫폼 스레드가 더 적합합니다.
synchronized블록 내에서 블로킹 I/O를 수행하면 캐리어 스레드가 고정(pinning)되므로,ReentrantLock으로 대체하는 것이 좋습니다.- ThreadLocal 사용을 최소화하세요. Virtual Thread가 수백만 개 생성될 경우 메모리 누수의 원인이 됩니다.
- Spring Boot 3.2 이상에서는 설정 한 줄로 모든 요청 처리를 Virtual Thread로 전환할 수 있습니다.
- 모니터링 시
jcmd의 Thread dump에서 Virtual Thread도 확인 가능하며, JFR(Java Flight Recorder)로 성능을 추적하세요.
마무리
Virtual Thread는 Java 서버 애플리케이션의 처리량을 극적으로 향상시킬 수 있는 혁신적인 기능입니다. 기존 thread-per-request 모델을 유지하면서도 리액티브 프로그래밍 수준의 확장성을 얻을 수 있어, 복잡한 리액티브 코드를 작성할 필요가 없어집니다. Spring Boot, Quarkus 등 주요 프레임워크가 이미 지원하고 있으므로, 실무 도입을 적극 검토해볼 시점입니다.