1. 가상 스레드 (Virtual Threads)란?
2023.09.20 릴리즈 된 자바 21에 추가된 가상 스레드(Virtual Threads)라는 기능을 살펴보자.
가상 스레드는 경량 스레드로, 높은 처리량의 동시 어플리케이션을 작성, 유지 및 관찰하는 작업 공수를 크게 줄인다. OS스레드를 그대로 사용하지 않고 JVM 자체적으로 스케쥴링을 통해 사용할 수 있는 경량 스레드이며, 하나의 프로세스가 수십 - 수백만 스레드를 동시에 실행할수 있도록 설계되었다.
2. 자바의 전통적인 스레드
자바 개발자들은 근 30년동안 동시성 서버 어플리케이션의 처리를 위해 스레드에 의존해왔다. 모든 메서드의 구문들은 스레드 내부에서 실행되며, 1개의 요청을 1개의 스레드가 처리한다. 대표적으로 스프링은 멀티스레드 구조이기에, 여러 스레드의 실행이 동시에 발생하며 동시 요청이 많아질수록 스레드의 수 역시 증가한다. 각각의 스레드는 지역 변수를 저장하고 메소드 호출을하는 스택을 제공하며, 문제가 생겼을 때의 Context도 제공하는데, 예를들어 Exception은 동일 스레드 내에서의 메소드에 의해 throw/catch 된다. 그렇기 때문에 개발자는 스레드의 Stack trace로 문제를 추적할 수 있는 것이고, 그 외 Debugger (스레드의 메소드 내에서 구문을 차례로 훑어본다), Profiler(JFR) (여러 스레드의 행동을 시각화하여 스레드의 성능을 이해할 수 있도록 도와준다.)도 모두 스레드 기반으로 되어있다.
2-1. 전통적인 스레드의 한계점
- 기존 JDK의 스레드는 OS 스레드를 Wrapping한 것으로 사용가능한 스레드의 수가 하드웨어 수준보다 훨씬 적게 제한되어있다.
- OS 스레드는 생성, 유지 비용이 높고 갯수가 제한적이라 요청량에 비례하여 무한정 늘릴수 없다.
- 어플리케이션 코드가 플렛폼 스레드를 사용하면 실제로는 OS스레드를 사용하는 것이며, 이 스레드는 비용이 비싸기 때문에 스레드 풀을 사용하여 접근하는 방식으로 사용한다.
- Spring 같은 어플리케이션의 기본 처리방식은 Thread-per-request이다.
[Thread-per-request]
서버 어플리케이션은 일반적으로 서로 독립적인 유저의 동시 요청들을 처리하기에, 어플리케이션이 전체 요청기간 동안 스레드를 전담하여 요청을 처리해야한다. 이러한 Thread-per-request 스타일은 플랫폼의 동시성 단위가 곧 어플리케이션의 동시성 단위이기 때문에 이해하기 쉽고, 개발 및 디버그, 프로파일링 하기 쉽다.
- Thread-per-request 방식은 요청을 처리하는 스레드에서 I/O 작업시 Blocking이 발생한다.
- Blocking 발생시 스레드는 I/O 작업 종료시까지 대기해야하기에 많은 요청을 처리해야 하는 상황이라면 Blocking으로 발생하는 낭비를 줄여야한다.
[Reactive Programming]
- Blocking 방식으로 발생하는 낭비를 줄이기 위해 발전하게된 처리량을 높이기 위한 방법, 비동기 방식 프로그래밍이다.
- Non-blocking 방식으로 변경하면서 Blocking을 대기하는데 소요된 자원을 다른 요청에서 사용할 수 있다.
- 기존 자바 프로그래밍은 스레드를 기반으로하기에 라이브러리들이 모두 Reactive Programming 방식에 맞게 새로 작성되어야하는 문제가 있다.
3. 가상 스레드의 작동방식
가상 스레드는 OS를 Wrapping한 구조가 아니기에 스레드 풀 없이 사용 가능하고, JVM 자체적으로 OS스레드와 연결하는 스케쥴링을 처리하기에 기존 스타일로 코드를 작성하더라도 내부의 가상 스레드가 효율적인 방법으로 스케줄링 해준다. (가상 스레드를 사용하면 Non-blocking에 대한 처리를 JVM단에서 처리해준다.)
4. 목표
공식 문서에 따르면 가상 스레드의 목표와 목표가 아닌 것 (Goals / Non-Goals)을 확인할 수 있다.
목표 (Goals)
- 기존의 Thread-per-request (요청당 처리) 방식으로 작성된 서버 어플리케이션을 near-optimal(최적화) 하드웨어 사용으로 확장 가능해야한다.
- java.lang.Thread API를 사용하는 기존 자바 코드를 최소한의 수정으로 가상 스레드를 채택 가능해야 한다.
- 기존 JDK 툴들을 사용하여 가상 스레드의 쉬운 트러블 슈팅, 디버깅 및 프로파일링이 가능해야 한다.
목표가 아닌 것 (Non-Goals)
- 기존 thread의 사용을 제거하는 것이나 기존 어플리케이션이 가상 스레드를 사용하기 위해 은밀하게 마이그레이션 하는 것이 아니다.
- 자바의 기본 동시성 모델을 바꾸는 것이 아니다.
- 자바 언어나 자바 라이브러리에 새로운 데이터 병렬구조를 제공하려는 것이 목표가 아니다. Stream API는 큰 데이터를 병렬로 처리하는데 여전히 선호되는 방법이다.
가상 스레드는 자바의 기본 동시성 모델을 바꾸거나, 새로운 데이터 흐름의 병렬 구조를 제시하는 것이 아닌 기존 자바 코드를 최소한으로 수정하는 선에서 동시성을 제어하는 어플리케이션이 기존 Thread-per-request 방식 외에 가상 스레드 풀 없이 Reactive Programming이 추구하는 Non-blocking의 효율적인 자원사용을 지원하는데 목표를 두고 있다.
참고
https://mangkyu.tistory.com/309
'Java' 카테고리의 다른 글
[Java] 자바 스트림(Streams)의 개념과 사용 방법 (1) | 2024.01.30 |
---|---|
[Java] Switch와 else-if의 효율성 비교 (Switch와 else-if 중에 어떤 걸 사용해야 할까?) (1) | 2023.10.24 |
[Java] 클래스 로딩 과정(Java Class Loading Process)이란? (0) | 2023.10.23 |