본문 바로가기
개발 잡담

Virtual Thread 가 뭣이당가?

by 휴일이 2025. 10. 5.

이번에 사이드 프로젝트를 하며 최신 기술을 사용해보기로 결심하여 

Java 25, Spring Boot 3.5.6 을 사용해보기로 했다.

 

나는 이전에 Java 17 을 사용했었기 때문에 17 -> 25까지의 변경점을 확인해보았는데

그 중에 "가상 스레드"라는 개념이 있어서 이게 뭔가 하고 살펴보았다.

- 참고로 Java 21 에서 추가된 기능임.

 

 

일단 Oracle 에서 가상 스레드 설명을 가져왔다.

 

 

 

내가 이해한 것은

"스레드는 맞는데 OS 스레드를 이용하는 거도 맞는데... 근데 가상 스레드가 OS 스레드는 아님;;;(?)" 이라는 느낌?

 

흠 그러니까..

 

- 이전 버전의 스레드는, 실제 OS 스레드와 1:1 매핑되어 사용함(OS 스레드를 사용하는 것)

- 가상 스레드는, 실제 OS 스레드를 사용하는 것은 맞지만 그 스레드의 유휴 상태를 최대한 방지하기 위하여 OS 스레드가 I/O 등을 기다릴 때 그 스레드가 다른 작업을 하도록 함

마치 코루틴을 활용하여 OS 스레드를 최대한 굴린다(ㅋㅋ)는 생각이 들었음.

이벤트 루프가 생각나기두 하구..

 

 

가상 스레드와 일반 스레드 비교

import java.util.ArrayList;
import java.util.List;

public class CompareVirtualAndPlatform {

    enum Mode { VIRTUAL, PLATFORM }

    public static void main(String[] args) throws Exception {
        // 사용법: java CompareVirtualAndPlatform [N] [sleepMillis] [repeats]
        int N = args.length > 0 ? Integer.parseInt(args[0]) : 1_0000;   // 스레드(작업) 개수
        int sleepMillis = args.length > 1 ? Integer.parseInt(args[1]) : 1_0; // 각 작업 지연
        int repeats = args.length > 2 ? Integer.parseInt(args[2]) : 3; // 반복 횟수(평균용)

        System.out.printf("N=%d, sleep=%dms, repeats=%d%n", N, sleepMillis, repeats);

        // 간단 워밍업 (JIT 편차 줄이기)
//        runOnce(Mode.PLATFORM, 100000, 10);
//        runOnce(Mode.VIRTUAL,  100000, 10);

        double platAvg = measureAvg(() -> runOnce(Mode.PLATFORM, N, sleepMillis), repeats);
        double virtAvg = measureAvg(() -> runOnce(Mode.VIRTUAL,  N, sleepMillis), repeats);

        System.out.printf("Platform avg: %.3f s%n", platAvg);
        System.out.printf("Virtual  avg: %.3f s%n", virtAvg);
    }

    /** 한 번 실행(총 완료 시간 측정) */
    private static void runOnce(Mode mode, int N, int sleepMillis) {
        long t0 = System.nanoTime();

        List<Thread> threads = new ArrayList<>(N);
        for (int i = 0; i < N; i++) {
            Thread t = (mode == Mode.VIRTUAL)
                    ? Thread.ofVirtual().unstarted(() -> doWork(sleepMillis))
                    : Thread.ofPlatform().unstarted(() -> doWork(sleepMillis));
            threads.add(t);
        }

        // 시작
        for (Thread t : threads) t.start();
        // 완료 대기
        for (Thread t : threads) {
            try { t.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        }

        long t1 = System.nanoTime();
        double sec = (t1 - t0) / 1_000_000_000.0;
        System.out.printf("[%s] N=%d, sleep=%dms -> %.3f s%n",
                mode, N, sleepMillis, sec);
    }

    /** 각 스레드가 수행하는 작업(블로킹 I/O 대기 대체: sleep) */
    private static void doWork(int sleepMillis) {
        try {
            Thread.sleep(sleepMillis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    /** n회 반복 평균 시간 측정 */
    private static double measureAvg(Runnable r, int repeat) {
        long sum = 0;
        for (int i = 0; i < repeat; i++) {
            long s = System.nanoTime();
            r.run();
            long e = System.nanoTime();
            sum += (e - s);
        }
        return sum / (repeat * 1_000_000_000.0);
    }
}

 

GPT 에서 긁어온 가상스레드/플랫폼스레드 예시다.

Mode 에서 어떤 스레드를 사용할지 결정하고 실행한다. 스레드는 n초간 쉬는 동작만 한다.

 

예상컨데 플랫폼 스레드는 sleep 을 할 동안 그냥 쉴 뿐이지만

가상스레드는 sleep 할 때에 다음 스레드 작업을 계속 할 것이기 때문에

가상스레드가 플랫폼스레드보다 빠를 것임.

 

 

그래서 직접 돌려서 비교해봄.

 

 

Platform avg: 0.423 s
Virtual  avg: 0.032 s

 

가상스레드가 압도적으로 빠른 것을 볼 수 있다...

 

 

 

그래도 뭐 스레드가 작업을 점유하는 시간이 긴 CPU 바운드 작업에는 적합하지 않고

I/O 작업이 많아 OS스레드와 1:1 매핑할 시에 유휴 시간이 많은 상태에서 사용하기 좋은 방법인 것으로 보인다.

 

 

 

결론: OS 스레드가 넘무 불쌍하잔아 !!!

좀 쉬게해줘라..

 

진짜루...

컴퓨터가 사람이 되면......

가상 스레드를 개발한 당신들부터 없애버릴거라고!!! 😿

 

진짜 똑똒하고 무서운 사람들...ㄷㄷㄷㄷㄷ

 

728x90
반응형