Java 21는 (Java 8이후 3번째 LTS 버전) 23년 9월 19일에 릴리즈 된 버전입니다.
그중 핵심은 가상 스레드 Virtual Thread입니다. 이번 시간을 통해 Virtual Thread에 대해 알아보며 도입하게 된 이유, 장점, 주의할 점에 대해 간단히 알아보는 시간을 가져보겠습니다.
Java 진영에서는 애플리케이션에서 동시성 처리를 위해 스레드를 사용해왔습니다. 하지만 기존 Thread Per Request 방식의 애플리케이션에는 한계가 있었고 기존의 Java의 스레드 모델에서의 플랫폼 스레드를 가져다 Wrappig 해서 쓰는 구조는 I/O 작업시 Blocking되는 문제 까지있습니다.
예를들어 스레드 풀에 존재하는 스레드는 CPU를 가지고 요청을 처리할 때 파일 쓰기 같은 I/O 작업을 만나면 CPU를 OS에 반환하고 실행할 수 없는 상태(Non-Runnable)가 됩니다.

진행중이던 스레드가 작업을 중단하고 I/O 작업이 끝날 때까지 대기합니다. 그러다 I/O작업이 긑나면 남은 작업을 이어가고 스레드는 스레드 풀에 반환합니다. 만약 전체 모든 스레드가 Blocking 돼있느 상황이라면 새로운 요청이 와도 톰캣의 내부 큐에 들어오게 되고, 사용 가능한 스레드가 스레드풀에 반환되어야 요청이 실행됩니다. 수 많은 I/O 작업을 할때 Java의 스레드 동작 방식에서 비효율이 발생하게 됩니다. 조금 더 Java의 전통적인 스레드 모델을 자세히 알아보겠습니다.
기
위와 같이 Java의 스레드 모델에서 오는 Blocking 문제나 동기 문제등 스레드 문제를 해결하고자 하는 여러 방법이나 대안을 간단하게 요약해보겠습니다.
가상 스레드 란 기존의 전통적인 Java 스레드에 더하여 새롭게 추가되는 경량 스레드입니다. OS 스레드를 그대로 사용하지 않고 JVM 자체적으로 내부 스케줄링을 통해서 사용할 수 있는 경량의 스레드를 제공합니다. 하나의 Java 프로세스가 수십만~ 수백만개의 스레드를 동시에 실행할 수 있게끔 설계되었습니다.
위의 내용을 요약해보자면 기존 Java의 스레드 모델에는 다음과 같은 문제가 있습니다.
그래서 Jdk21 에서는 위의 문제를 해결하고자 다음을 목표로 삼았습니다.
Virtual Thread의 특징에 대해 알아보겠습니다.
즉 가상 스레드 는 기존 스레드 방식의 이점을 누리면서도 Reactive programming의 장점을 취할 수 있다.
참고자료: https://techblog.woowahan.com/15398/
여러 다른 기술 vs Virtual Thread
가상 스레드의 구조에 대해 알아보겠습니다.

기존 스레드는 스레드 풀을 사용하여 접근하는 방식을 사용했습니다.


전통적인 방식의 스레드에 비해 가상 스레드는 OS스레드를 감싼 구조가 아니고 JVM에서 자체적으로 가상 스레드를 OS 스레드와 연결하는 스케줄링을 합니다. 이 작업을 mount/unmount라고 하며 기존 플렛폼 스레드를 Carrier 스레드 라고 합니다. 여기서 중요한것은 가상 스레드 풀이란 것 없이 사용합니다.
여기서 스케줄링을 통해 큰차이가 발생하는데 기존의 스레드는 Blocking이 발생하면 대기 상태에 놓였지만 가상 스레드는 Blocking이 발생하면 내부 스케줄링을 통해 실제 작업을 처리하는 Carrier 스레드는 다른 가상 스레드의 작업을 처리하면됩니다.
Virtual Thread의 구조

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) throws Exception {
run();
}
public static void run() throws Exception {
// Virtual Thread 방법 1
Thread.startVirtualThread(() -> {
System.out.println("Hello Virtual Thread");
});
// Virtual Thread 방법 2
Runnable runnable = () -> System.out.println("Hi Virtual Thread");
Thread virtualThread1 = Thread.ofVirtual().start(runnable);
// Virtual Thread 이름 지정
Thread.Builder builder = Thread.ofVirtual().name("JVM-Thread");
Thread virtualThread2 = builder.start(runnable);
// 스레드가 Virtual Thread인지 확인하여 출력
System.out.println("Thread is Virtual? " + virtualThread2.isVirtual());
// ExecutorService 사용
try (ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i <3; i++) {
executorService.submit(runnable);
}
}
}
}
위의 코드를 보면 알 수 있듯 기존 플랫폼 스레드의 문법과 크게 차이가 없이 가상 스레드를 만들 수 있는것을 알 수 있습니다. Executors를 사용해 가상스레드를 만들고 측정 또한 가능합니다.
Java 가상 스레드를 만든 feature loom 팀이 하위호환성, 추상화에 얼마나 진심인지 알 수 있습니다.
참고자료: https://findstar.pe.kr/2023/04/17/java-virtual-threads-1/
참고자료: https://techblog.woowahan.com/15398/
여러 다른 기술 vs Virtual Thread
참고자료: https://www.youtube.com/watch?v=_lp3ohne-i8
단순 스레드를 여러번 실행시켰을 때 비교
Pinning이란?
synchronized 키워드를 사용한 코드 블럭 안에서 blocking IO작업을 수행하는 경우에는 가상 스레드 를 unmount 할 수 없어서 Carrier Thread(Platform Thread)까지 Blocking 되는 현상이 발생한다. (이를 pinning 이라고 지칭함)