6.6 Java 21 Virtual Threads & Structured Concurrency (Project Loom)
← Back to Chapter 6: Multithreading & Concurrency
Java 21 introduced Virtual Threads as an official LTS feature under Project Loom, fundamentally modernizing high-throughput server architecture.
1. Platform (OS) Threads vs Virtual Threads
Traditional Platform Threads (1:1 with OS Kernel Threads):
JVM Thread 1 ──────► Linux Kernel pthread (1 MB fixed stack memory)
JVM Thread 2 ──────► Linux Kernel pthread (1 MB fixed stack memory)
* Limited to ~5,000–10,000 total threads before OS context-switch thrashing
Java 21 Virtual Threads (M:N User-Space Schedulers):
[ Millions of Virtual Threads ] (few hundred bytes on JVM Heap)
│ (Dynamic unmounting during blocking I/O)
▼
[ Carrier OS Threads ] (Equal to number of available CPU cores)
▼
[ CPU Hardware Cores ]
| Dimension | Platform Threads (Thread) | Virtual Threads (VirtualThread) |
|---|---|---|
| OS Mapping | 1:1 with OS kernel pthread | M:N (millions mapped to a few carrier threads) |
| Memory Footprint | ~1 MB fixed OS stack space | ~几百 bytes starting memory (dynamically sized on heap) |
| Creation Cost | Expensive kernel system call | Cheap Java heap object allocation |
| Blocking I/O | OS thread stays blocked, wasting RAM | Thread unmounts from carrier thread, freeing carrier for others |
| Best Used For | CPU-bound calculations | I/O-bound microservices (HTTP, DB, Sockets) |
2. Launching Virtual Threads
// Method 1: Dedicated Executor (One virtual thread per task)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1)); // Unmounts carrier thread!
return i;
});
});
} // Blocks until all 10,000 virtual tasks finish
// Method 2: Fluent Thread Builder
Thread vThread = Thread.ofVirtual().name("payment-worker-", 1).start(() -> {
System.out.println("Executing on carrier: " + Thread.currentThread());
});
3. The Thread Pinning Caveat
A virtual thread cannot unmount from its carrier thread during blocking I/O if it is pinned:
- Cause 1: Running inside a
synchronizedblock or method. - Cause 2: Executing native code via JNI.
- Solution: Replace
synchronizedblocks in I/O-intensive code withjava.util.concurrent.locks.ReentrantLock.