Virtual Thread in JAVA
작업마다 Java Thread 를 생성하면 OS 단위에서 Thread 를 생성하기 때문에 비싸고 시간도 오래 걸린다. 그래서 Java 진영에선 기존에 생성하던 Java Thread 를 Platform Thread 라고 정의하고 Virtual Thread 라는 것을 새로 만들어서 Platform Thread 위에서 실행되는 구조를 잡았다.

Virtual Thread 는 Heap 메모리에 있는 객체다. Virtual 스레드는 생성 작업이 JVM 위에서 이뤄지기 때문에 Platform Thread 에 비해서 생성 비용도 싸다. Stack 이나 IP 같은 저장공간도 JVM 내에서 컨텍스트 스위칭 되는 형태이기 때문에 불필요한 시스템 콜이 없어지게 된다
Java Thread 에 비해서 커널레벨까지 내려가서 실행하는 시스템 작업이 없어 효율적이다.
추가로 Virtual Thread 의 강점은 Blocking I/O 를 Non blocking I/O 로 실행하게 된다는 점이다.
기존 Java Thread 를 사용할 경우 I/O 작업이 오래 걸리면 끝까지 기다리게 된다. 반면에 Virtual Thread 에서는 JVM 이 Blocking 을 감지하면 실행하고 있던 Virtual Thread 를 중지하고 다른 Virtual Thread 를 실행한다.
그래서 하나의 Java Thread 를 사용해도 두개의 작업을 동시에 실행하는 효과를 내게 된다.
public class TEST {
private static final int TASKS = 1000;
public static void main(String[] args) {
long start = System.currentTimeMillis();
ioBoundTask();
System.out.printf("Time %dms\n", System.currentTimeMillis() - start);
}
private static void ioBoundTask() {
try (ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < TASKS; i++) {
executorService.submit(() -> {
for (int j = 0; j < 100; j++) {
System.out.println("Executing Blocking Task: " + Thread.currentThread());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
}
}
}
}
Virtual Thread 를 sleep 코드에서 blocking 이 발생하지 않아서 새로운 쓰레드 생성을 계속한다. 그래서 1000개의 쓰레드를 생성하는데 1233ms 만 소요된다
Executing Blocking Task: VirtualThread[#686]/runnable@ForkJoinPool-1-worker-5
Executing Blocking Task: VirtualThread[#482]/runnable@ForkJoinPool-1-worker-2
Executing Blocking Task: VirtualThread[#688]/runnable@ForkJoinPool-1-worker-2
Executing Blocking Task: VirtualThread[#635]/runnable@ForkJoinPool-1-worker-4
Executing Blocking Task: VirtualThread[#645]/runnable@ForkJoinPool-1-worker-8
Executing Blocking Task: VirtualThread[#937]/runnable@ForkJoinPool-1-worker-1
Executing Blocking Task: VirtualThread[#972]/runnable@ForkJoinPool-1-worker-6
Tasks took 1233ms to complete
newCachedThreadPool 을 사용하는 경우 Java Thread 를 생성해야 하기 때문에 시간이 더 소요되고 경우에 따라선 하드웨어 자원이 부족해 crash 가 발생하게 된다.
Executing Blocking Task: Thread[#286,pool-1-thread-259,5,main]
Executing Blocking Task: Thread[#1006,pool-1-thread-979,5,main]
Executing Blocking Task: Thread[#286,pool-1-thread-259,5,main]
Executing Blocking Task: Thread[#1006,pool-1-thread-979,5,main]
Tasks took 1760ms to complete