-
[Java] 자바 21 가상 스레드Java 2026. 6. 29. 20:21
비동기와 동기에 대해서 공부를 하던 중 2023.09.19부터 JDK 21 LTS는 가상 스레드가 도입되었다는 것을 알게 되었다.
그리고 Spring은 2023.11.17 부터, Spring Boot 3.2는 2023.11.23 부터 JDK 21을 지원하고 있다.
가상 스레드는 경량 스레드이다. OS 스레드를 그대로 사용하지 않고 JVM 내부 스케줄링을 통해서 수십만~수백만 개의 스레드를 동시에 사용할 수 있게 한다.
그러면 가상 스레드 도입 전에 Java는 어떻게 스레드를 관리하고 있었을까?
자바의 스레드는 OS Thread를 Wrapping 한 것이다. 이것을 Platform Thread라고 부른다.
더보기Wrapping: low-level의 시스템 자원을 개발자가 코드에서 쉽게 사용할 수 있도록 자바 객체로 포장해서 제공하는 것
OS Thread는 운영체제가 알고 있는 스레드이다. 그래서 생성 개수가 제한적이고 생성, 유지하는 비용이 비싸다.
그래서 애플리케이션에서 플랫폼 스레드를 효율적으로 사용하기 위해서 Thread Pool을 사용했다.
Thread Pool
Thread Pool은 스레드를 매번 새로 만들고 없애기엔 비용이 너무 비싸서, 미리 일정 개수를 만들어두고 작업이 들어올 때마다 배정해서 돌려쓰는 스레드 5분 대기조이다.

기본적인 spring mvc 에서 Thread Per Request 방식으로 하나의 요청 당 하나의 스레드를 할당했다.
하지만 OS Thread를 무한정 늘릴 수 없기 때문에 처리량이 제한되어 있었다.
그리고 IO Bound의 작업을 처리 중이면 스레드가 실제로 일하는 시간보다 대기하는 시간이 길어지게 된다.
Reactive Programming (Webflux)

Webflux는 비동기와 논블로킹 처리를 지원하기 위해 만들어진 리액티브 웹 프레임워크이다.
스레드가 IO작업을 요청하고 결과가 도착할 때까지 기다리는 것이 아니라 다른 작업을 수행한다.
Reactive Programming을 사용하려면 모든 것이 논블로킹이어야 한다.
하지만 우리가 흔히 사용하는 JPA는 동기/블로킹 방식이다. 그래서 비동기를 지원하는 R2DBC 같은 ORM을 사용해야 한다.
문제점: 자바의 디자인은 스레드 중심으로 되어있다.
이 말은 Exception Stack Trace, Debugger, Profiling 모두 스레드 기반이다.
Reactive 할 때 작업이 여러 스레드를 걸쳐서 이루어지는데, 컨텍스트 확인이 어려워 디버깅이 어려워진다.
기존의 문제점
1. 처리량의 한계
2. Blocking으로 인한 리소스 낭비
3. Reactive Programming의 단점
가상 스레드의 목적
1. 개발자가 하드웨어 성능을 잘 활용하는 높은 처리량의 서버를 작성하는 것
- 가상 스레드는 블로킹이 발생하면 부적으로 스케줄링을 활용하여 플랫폼 스레드가 그냥 대기하게 두지 않고 다른 가상 스레드가 작업할 수 있도록 한다.
2. 자바 플랫폼의 디자인과 조화를 이루는 코드를 생성할 수 있도록 하는 것
- 가상 스레드는 기존 스레드 구조를 그대로 사용하기 때문에 디버깅, 프로파일링등 기존의 도구도 그대로 사용할 수 있다.
플랫폼 스레드와 가상 스레드의 차이점

플랫폼 스레드는 운영체제가 직접 스케줄링하고 JVM이 스레드를 매칭할 때 Java Native Interface를 통해 커널에 Naitive Tread 생성을 요청해 자바의 스레드와 1 : 1 로 매핑하는 구조이다.

기존의 스레드는 블로킹이 발생하면 그냥 기다려야 했는데, 가상 스레드는 블로킹이 발생하면 내부의 스케줄링을 통해서 실제 작업을 처리하는 캐리어 스레드는 다른 가상 스레드의 작업을 처리하면 된다. 따라서 논블로킹의 장점을 가지게 된다.
사용하는 자원의 차이
플랫폼 스레드 가상 스레드 메타 데이터 사이즈 약 2kb 200 ~ 300 B 메모리 미리 할당된 Stack 사용 필요시 마다 Heap 사용 컨텍스트 스위칭 비용 1 ~ 10us ns (or 1us 미만)
가상 스레드 동작 원리

1. 실행될 가상 스레드 작업인 runContinuatioin을 carrier thread의 workQueue에 push 한다.
2. Work queue에 있는 runContinuation들은 forkJoinPool에 의해 work stealing 방식으로 carrier thread에 의해 처리된다.
3. 처리되던 runContinuation 들은 I/O, Sleep으로 인한 interrupt나 작업 완료 시, work queue에서 pop되어 park 과정에 의해 다시 힙 메모리로 되돌아간다.
요약
- 기존 플랫폼 스레드: 무거운 OS 스레드 기반으로, I/O 대기 시 자원 낭비와 처리량 한계 발생
- 리액티브 (WebFlux): 성능은 올렸으나 코드가 복잡해지고 디버깅이 어려워지는 단점 존재
- 가상 스레드 (JDK 21): JVM이 직접 스케줄링하는 초경량 스레드로, 적은 자원으로 수백만 개 생성 가능
- 핵심 의의: 익숙하고 쉬운 동기식 코드와 디버깅 환경을 유지하면서, 비동기 수준의 높은 처리량을 달성
다음에는 실제로 얼마나 좋은지 알아보자