Java 32: Virtual Threads (Project Loom)
easy⏱ 5 mincoursejava
Platform vs virtual threads
Platform thread: 1:1 OS thread, ~1MB stack, expensive (few thousand per JVM). Virtual thread: JVM-scheduled, stack stored on heap + resized, ~100 bytes when idle. A single OS thread (called carrier) runs many virtual threads cooperatively via a continuation mechanism.
Pinning and blocking
A virtual thread unmounts from its carrier when it hits a blocking op (Thread.sleep, socket.read). Carrier is free to run another VT. It pins (can't unmount) when: (1) inside synchronized block, (2) in a native call, (3) in a native library. For high-throughput I/O, replace synchronized with ReentrantLock.
Spawning
Thread.ofVirtual().start(runnable) or Thread.startVirtualThread(runnable). Executors.newVirtualThreadPerTaskExecutor() gives you an ExecutorService that spawns one VT per task — the canonical replacement for newFixedThreadPool(N) in server code.
try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
for (Request r : requests) exec.submit(() -> handle(r));
} // try-with-resources closes and awaits
Simulate VT vs platform
Simulate runN(threadsMax, tasks, taskDurationMs) twice: platform mode caps at threadsMax = 200, virtual mode has no cap. Submit 10000 fake 'sleeps' of 10ms. Log total wall time — platform serializes in batches; virtual runs all concurrently.
Don't pool virtual threads
The point of VTs is create-and-discard. Executors.newFixedThreadPool(10) with VTs makes no sense — you're just capping what was meant to scale. Use newVirtualThreadPerTaskExecutor() and let the JVM handle scheduling.
Quiz: When do VTs NOT help
When are virtual threads the wrong tool? CPU-bound work. VTs give you concurrency, not parallelism. You still have N cores; 1M VTs doing Math.sqrt are just fighting for N carriers. Use a fixed pool sized to your core count for CPU work.