들어가며
과거 동시 처리 기능을 구현하는 경우, 스레드(Thread)의 무분별한 사용은 운영체제(OS)가 직접 스레드를 관리하기 때문에 높은 메모리 사용량과 문맥 교환(Context Switching) 비용이 발생하여 심각한 성능 저하로 이어졌다. 특히, 웹 서버 및 데이터베이스와 같은 높은 동시성을 필요로 하는 애플리케이션에서의 기존 스레딩(Threading) 모델을 사용한 동시 처리는 많은 문제들을 발생 시켰다. 이러한 성능 저하를 막기 위해 우리는 스레드 수를 제한하거나 관리를 위한 스레드 풀(Pool)을 만들어두고 사용했다. 하지만 Java 21에서 가상 스레드(Virtual Thread) 라는 기능이 공식 지원되며 이러한 문제를 손쉽게 해결 가능해졌다.
이 새로운 스레딩 모델은 기존 플랫폼 스레드(Platform Thread)의 비효율성을 해결하여 Java 애플리케이션의 동시성과 확장성을 크게 향상시키는 것을 목표로 한다. 본 리포트에서는 가상 스레드가 무엇인지, 플랫폼 스레드와의 차이점 그리고 실제 현업 프로젝트에서 가상 스레드 기반의 개발 방법론과 얻어지는 효과에 대해 소개한다.
* 플랫폼 스레드는 Java21 이전의 java.lang.Thread 객체가 OS 스레드를 감싼 형태의 래퍼 클래스
Virtual Thread
가상 스레드는 Java 21에서 도입된 사용자 모드 경량 스레드이다. 기존 플랫폼 스레드는 OS 스레드와 1:1로 연결되는 반면, 가상 스레드는 JVM(Java Virtual Machine)이 직접 관리하며 플랫폼 스레드 위에서 다중화 된다. 이를 통해 최소한의 오버헤드로 대량의 동시 실행을 지원할 수 있다.
* JVM은 Java 프로그램이 실행되는 실행 환경(runtime environment)을 제공하는 소프트웨어 기반의 가상 머신
[그림1] 플랫폼 스레드
[그림2] 가상 스레드
Thread.ofVirtual() 메서드(method)를 통해 생성할 수 있으며, 기존의 스레드 API(Application Programming Interface)를 그대로 사용할 수 있다는 장점이 있다. 가상 스레드는 대기 시간이 긴 I/O (Input/Output) 작업에서 높은 성능을 발휘하는데 이는 I/O 작업 시 대기 상태에 들어가면, JVM이 가상 스레드를 플랫폼 스레드에서 해제하고 해당 리소스를 재활용하여 다른 작업을 수행할 수 있도록 하기 때문이다. 또한 기존 플랫폼 스레드가 운영체제의 스레드 개수 제한을 받았던 것과 달리 백만 개 이상의 스레드를 실행할 수 있도록 설계되어 높은 동시 처리 능력을 가진다.
* I/O 작업은 입력(Input), 출력(Output)의 약자로 컴퓨터 시스템에서 주로 데이터를 주고 받거나 처리하는 모든 과정을 의미
Virtual Thread 톺아보기
가상 스레드에 대하여 소스 단위에서 자세히 알아보도록 한다.
[그림 3] Java21 Library 내부
[그림 3] 에서 보면 Java21 이전에는 볼 수 없었던 ‘VirtualThread’라는 Class를 볼 수 있다.
해당 Class를 자세히 들어다 보도록 하자.
‘VirtualThread’ 클래스 내부에는 위와 같이 각 스레드의 상태 값을 정의하며 관리하고 있다.
Java 21에서는 13가지 상태로 스레드를 관리해 왔으며, 2025년 3월에 출시한 Java 24에서 7가지가 늘어난 20가지 상태 값으로 더 정밀한 스레드 관리를 하고 있음을 확인 할 수 있다.
Java 21에서는 스레드 내부에서 synchronized와 같은 메서드 동작이 있는 경우 가상 스레드가 플랫폼 스레드에서 올라간 후 내려올 수 없는 상태 (Pinned) 가 되어 성능 저하를 유발할 수 있었으나, Java 24에서 이를 식별하는 모니터 기능이 추가되고 이를 추가된 상태 값으로 관리하여 이 문제가 개선되었다.
* synchronized는 Java에서 스레드 안전성(thread-safety)을 보장하기 위해 사용되는 메서드로, 동시에 여러 스레드가 해당 메서드를 실행하지 못하도록 잠금을 거는 기능
클래스의 함수 구현 부분을 보면, 상태 값들을 사용하여 JVM이 직접 스레드를 관리하며 실행이 필요할 때 캐리어 스레드(Carrier Thread)에 마운트(Mount) 하여 실행하는 것을 확인 할 수 있다.
가상스레드의 경우 I/O 작업을 수행할 때 캐리어 스레드에서 마운트를 해제한다.
이후 I/O 작업이 완료될 준비가 되면 가상스레드를 JDK(Java Development Kit) 스케줄러로 마운트 요청을 하며, JDK 스케줄러는 캐리어 스레드에 마운트하여 코드 실행을 재개한다.
* 캐리어 스레드는 Java21 에서 Virtual Thread가 도입되면서 생긴 새로운 개념, Virtual Thread를 실행하는 플랫폼 스레드의 종류
* JDK는 Java 기반 어플리케이션을 개발하기 위한 개발 도구 모음
Virtual Thread vs Platform Thread
위에서 가상 스레드에 대해 자세히 알아보았다. 하지만 실제 얼마만큼 기존 플랫폼 스레드에 비해서 수치상으로 우수한지 테스트가 필요하다. 아래에는 가상 스레드와 플랫폼 스레드의 차이를 테스트를 통해 비교한다.
테스트 방법
테스트에서는 다수의 작업을 생성하고, 스레드 간의 경쟁 상황을 통해 어떤 스레드가 더 빠르게 처리하는지를 확인한다. 본 리포트에서는 작업(Task) 수를 1,000개에서 10,000개까지 늘려가며 테스트를 진행하였고, 작업의 반복 횟수(COMPUTATION_ITERATIONS)를 1,000,000으로 지정하여 적절한 부하를 주었다.
테스트 환경
테스트 코드
테스트 결과
테스트 결과, 1,000개에서 2,000개의 작업을 처리했을 때는 큰 차이가 나지 않았으나, 5,000개를 넘어가면서 2배 이상의 처리 속도 차이를 보였다. 10,000개 이상의 작업에서는 플랫폼 스레드 가 동작을 멈추는 현상이 발생했다. 이러한 테스트를 통해 가상 스레드가 동시성 작업이 많을 때 더욱 빠르고 안정적인 처리 성능을 제공함을 확인할 수 있다.
Virtual Thread 적용 해보기
기존 프로젝트에 가상 스레드 적용
기존 ExecutorService 코드
가상 스레드 적용 코드
이처럼 Executors.newVirtualThreadPerTaskExecutor()를 사용하면 기존의 ThreadPoolExecutor와 유사한 방식으로 가상 스레드를 활용할 수 있다.
가상 스레드 적용 코드를 보면 스레드 풀의 크기를 지정해 주는 코드가 없는데, 이것은 가상 스레드의 특성상 스레드의 생성 및 종료 비용이 매우 낮기 때문에 스레드 풀의 크기를 명시적으로 제한할 필요가 없기 때문이다.
Spring Boot에서 가상 스레드 적용
스프링 부트 3.2 이상에서는 ‘application.yml’의 어플리케이션 설정만으로 가상 스레드를 쉽게 활성화할 수 있다.
위 설정을 추가하면 스프링 MVC(Mode-View-Controller) 서블릿(Servlet)에서 가상 스레드를 자동으로 활성화하도록 설정된다. 이를 통해 서블릿 요청 처리를 더욱 효율적으로 수행할 수 있다.
Virtual Thread의 한계
지금까지 가상스레드의 기술적 우수함과 높은 성능에 대해 살펴보았다. 하지만 가상 스레드는 일부 한계를 가지고 있다. 상황에 따라서 기존 플랫폼 스레드 보다 성능이 떨어질 수 있어, 아래 정리된 사항들을 충분히 이해하고 신중하게 도입하는 것이 중요하다.
- CPU 바운드 작업에는 부적합: 가상 스레드는 문맥 교환이 자주 발생하는 I/O 바운드 작업에 최적화되어 있어, 단순히 CPU를 많이 사용하는 작업에는 여전히 플랫폼 스레드가 적합하다.
- JVM 가비지 컬렉션(GC) 부담: 생성 비용이 적어서풀을 만들어 관리하지 않기 때문에 소멸 과정 시에 GC에 의해 제거 된다. 백만 개의 스레드를 생성할 수 있게 설계된 가상 스레드는 이후 소멸 과정에서 가비지 컬렉션에 큰 부담을 줄 수 있다.
- 연동 노드의 부담: 어플리케이션이 데이터베이스나 타 어플리케이션과 연동하는 경우가상 스레드의 높은 가용성이 다른 노드의 과도한 부담으로 이어져 시스템 장애를 유발할 수 있다.
마치며 : 시사점
Java 21에서 도입된 가상 스레드는 동시성 및 확장성을 크게 향상하기 위한 혁신적인 기술이다. 동시성 작업이 많은 웹 서버, 데이터베이스와 같은 동시성 애플리케이션에서 중요한 역할을 할 수 있으며, 기존의 플랫폼 스레드의 한계를 극복할 수 있는 강력한 도구이다.
이번 리포트에서는 가상 스레드 의 개념과 이점, 그리고 실제 프로젝트에 적용하는 방법에 대해 다루었다. 자바 개발자들은 이제 더 이상 다른 언어나 기술로 전환할 필요 없이, Java에서 제공하는 강력한 동시성 처리 기능을 활용하여 생산성을 높이고 시스템 자원을 효율적으로 사용할 수 있게 되었다.
스프링 부트(Spring Boot)와 같은 프레임워크에서도 가상 스레드를 손쉽게 활성화할 수 있는 설정을 제공하여, 개발자들이 애플리케이션을 변경하는 작업을 더욱 간소화하였고 Java 24에서는 초기 버전의 한계를 극복하며 지속해서 개선하고 있다. 이를 통해 Java 생태계 내에서 동시성 처리의 표준을 한 단계 끌어올릴 수 있을 것으로 기대된다.
최근 자바 개발자들이 코틀린(Kotlin) 등의 언어로 전환하는 추세를 관찰할 수 있었지만, 이번 가상 스레드의 도입으로 인해 자바의 동시성 처리 능력이 다시 한번 주목받게 되었다.
하지만 신기술의 무분별한 적용보다는 앞서 설명한 한계점과 관련된 시스템 전체의 사이드 이펙트(Side Effect)를 충분히 검토하고 최신 트렌드와 관련된 정보를 지속해서 학습하여 기술 적용을 해야 할 것은 분명하다.
# References
https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html
https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Thread.html
https://openjdk.org/jeps/491
https://github.com/openjdk/jdk24u

최중륜 프로
소프트웨어사업부 솔루션사업팀
삼성그룹의 메일 엔진 설계/개발을 담당하고 있습니다.
-
다음 글다음 글이 없습니다.
Register for Download Contents
- 이메일 주소를 제출해 주시면 콘텐츠를 다운로드 받을 수 있으며, 자동으로 뉴스레터 신청 서비스에 가입됩니다.
- 뉴스레터 서비스 가입 거부 시 콘텐츠 다운로드 서비스가 제한될 수 있습니다.
- 파일 다운로드가 되지 않을 경우 s-core_mktg@samsung.com으로 문의해주십시오.