Virtual Threads (Project Loom, Java 21) là thread cực nhẹ do JVM quản lý — không phải OS thread.
Bật trong Spring Boot 3.2:
# application.yml — một dòng là xong
spring:
threads:
virtual:
enabled: trueSpring Boot tự dùng Virtual Thread cho Tomcat, @Async, @Scheduled.
Tại sao Virtual Threads tốt:
- OS thread: tốn ~1MB stack, context switch nặng → giới hạn ~few thousands.
- Virtual Thread: tốn ~KB, triệu cái đồng thời → idle khi block I/O (DB call, HTTP).
// Code BLOCKING thông thường — giờ scale tốt hơn với VT
@RestController
class UserController {
@GetMapping("/users/{id}")
User get(@PathVariable Long id) {
return userRepo.findById(id).orElseThrow(); // blocking DB call
// Virtual Thread block → unmount khỏi carrier thread
// carrier thread phục vụ request khác trong lúc chờ DB
}
}So sánh với WebFlux (Reactive):
| Virtual Threads | WebFlux (Reactive) | |
|---|---|---|
| Code style | Blocking (quen thuộc) | Non-blocking (Mono/Flux) |
| Complexity | Thấp | Cao (reactive learning curve) |
| Khi dùng | I/O-bound, CRUD app | High-throughput streaming |
| CPU-bound | Không giúp ích | Không giúp ích |
Lưu ý quan trọng:
- MDC context bị leak với Virtual Threads (thread-local reuse) — dùng Micrometer Observation API thay raw MDC.
- synchronized block pin Virtual Thread vào OS thread → triệt tiêu lợi ích, dùng ReentrantLock thay.
- Không cải thiện CPU-bound task — chỉ hiệu quả với I/O-bound.
Virtual Threads (Project Loom, Java 21) are ultra-lightweight threads managed by the JVM — not OS threads.
Enable in Spring Boot 3.2:
# application.yml — one line is all it takes
spring:
threads:
virtual:
enabled: trueSpring Boot automatically uses Virtual Threads for Tomcat, @Async, and @Scheduled.
Why Virtual Threads are beneficial:
- OS threads: ~1MB stack each, heavy context switching → limited to a few thousand.
- Virtual Threads: ~KB each, millions can coexist → unmount from the carrier thread when blocking on I/O (DB calls, HTTP).
// Normal BLOCKING code — now scales much better with VT
@RestController
class UserController {
@GetMapping("/users/{id}")
User get(@PathVariable Long id) {
return userRepo.findById(id).orElseThrow(); // blocking DB call
// Virtual Thread blocks → unmounts from carrier thread
// carrier thread serves another request while waiting for DB
}
}Comparison with WebFlux (Reactive):
| Virtual Threads | WebFlux (Reactive) | |
|---|---|---|
| Code style | Blocking (familiar) | Non-blocking (Mono/Flux) |
| Complexity | Low | High (reactive learning curve) |
| Use when | I/O-bound, CRUD apps | High-throughput streaming |
| CPU-bound | No benefit | No benefit |
Important caveats:
- MDC context leaks with Virtual Threads (thread-local reuse) — use the Micrometer Observation API instead of raw MDC.
- synchronized blocks pin the Virtual Thread to an OS thread → eliminates the benefit. Use ReentrantLock instead.
- No improvement for CPU-bound tasks — only effective for I/O-bound workloads.