Back to results for “Concurrency”
Java › Concurrency & Threads

How does Java's virtual threads feature (Project Loom, finalized in Java 21) fundamentally change the thread-per-task model for high-concurrency applications, and how do they differ from traditional platform threads?

Advanced

Answer

Virtual threads are lightweight threads managed by the JVM itself (not directly mapped one-to-one to an OS thread), allowing an application to create potentially millions of virtual threads without the memory and context-switching overhead that would make an equivalent number of traditional platform threads impractical -- when a virtual thread performs a blocking operation (like I/O), the JVM automatically unmounts it from its underlying OS "carrier" thread and mounts a different virtual thread instead, letting a small pool of actual OS threads efficiently serve a vastly larger number of concurrent virtual threads, fundamentally enabling a simple "thread-per-request" programming model (easier to reason about than reactive/async code) to scale to workloads previously requiring complex asynchronous frameworks.
// Traditional: limited by OS thread overhead, typically capped around a few thousand concurrent threads
ExecutorService platformPool = Executors.newFixedThreadPool(200);

// Virtual threads: can create millions, each cheap, automatically unmounted during blocking I/O
try (ExecutorService virtualExecutor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 100_000; i++) {
        virtualExecutor.submit(() -> {
            // blocking I/O call here doesn't tie up a scarce OS thread
            makeBlockingHttpCall();
        });
    }
}
Real-world example A high-throughput API gateway migrates from a complex reactive (non-blocking, callback-heavy) architecture to a simple thread-per-request model using virtual threads, achieving comparable throughput and resource efficiency to the reactive approach while dramatically simplifying the codebase back to straightforward, easy-to-debug synchronous-looking code.

Common follow-ups: What specific blocking operations cause a virtual thread to be unmounted from its carrier thread, and are there operations that still "pin" a virtual thread (blocking the carrier)?;How does this change the calculus around whether to adopt a reactive programming model versus simple thread-per-request going forward?