이번에 사이드 프로젝트를 하며 최신 기술을 사용해보기로 결심하여
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 스레드가 넘무 불쌍하잔아 !!!

진짜루...
컴퓨터가 사람이 되면......
가상 스레드를 개발한 당신들부터 없애버릴거라고!!! 😿
진짜 똑똒하고 무서운 사람들...ㄷㄷㄷㄷㄷ
'개발 잡담' 카테고리의 다른 글
| @Transactional 은 최소화, fetch join 최대화 (1) | 2025.12.18 |
|---|---|
| DB 논리적 제약 조건일 때 애플리케이션에 검증 코드를 추가해야 하는가? (0) | 2025.12.18 |
| 인생 처음으로 개발자로서 지냈던 지난 1년을 회고하며 (2) | 2024.12.19 |
| DB ) N+1 문제는 왜 나쁜 걸까? (1) | 2024.12.09 |
| "혼자 공부하는 컴퓨터 구조 + 운영체제"를 마치며 (4) | 2024.10.22 |