Understanding Java Virtual Threads: Principles, Implementation, and Performance
Java virtual threads, introduced in JDK 21, decouple Java threads from OS threads by using continuations and a ForkJoinPool scheduler, allowing millions of cheap, blocking‑friendly threads that boost throughput and simplify concurrent code compared to traditional platform threads or reactive frameworks, as demonstrated by performance benchmarks.
Virtual threads are Java threads implemented by the Java runtime rather than the operating system. Unlike traditional platform threads, many virtual threads (even millions) can run concurrently in the same Java process, enabling higher throughput for thread‑per‑request servers.
The author experimented with JDK 21 to explore the principles and implementation of virtual threads.
Background
High‑throughput concurrent applications require many threads, but each platform thread consumes ~1 MB of stack memory and is limited by the OS. The diagram shows the maximum number of OS threads.
In most JVMs, Java threads map one‑to‑one to OS threads, so a thread‑per‑request model quickly reaches the OS thread limit.
When the workload is I/O‑bound, most platform threads spend time blocked, leading to low CPU utilization.
Reactive programming avoids this by returning threads to a pool while waiting for I/O, but it introduces complexity and debugging difficulties.
Virtual threads solve these problems by decoupling Java threads from OS threads, similar to virtual memory. The runtime maps many virtual threads onto a few OS threads, giving the illusion of abundant threads.
Platform threads (java.lang.Thread) are thin wrappers of OS threads; virtual threads are also instances of java.lang.Thread but are not bound to a specific OS thread. When a virtual thread executes blocking I/O, the runtime suspends it and frees the carrier OS thread for other work.
Implementation
Key objects:
Continuation : wraps the user task; the virtual thread yields to it when blocking.
Scheduler : a ForkJoinPool that schedules tasks onto platform threads.
Carrier : the platform thread that actually runs the virtual thread’s code.
runContinuation : a Runnable that mounts the virtual thread onto a carrier, runs it, and unmounts it.
Example of the VirtualThread class (excerpt):
final class VirtualThread extends BaseVirtualThread {
private static final ForkJoinPool DEFAULT_SCHEDULER = createDefaultScheduler();
private final Executor scheduler;
private final Continuation cont;
private final Runnable runContinuation;
private volatile Thread carrierThread;
// constructor and runContinuation implementation …
}Creating virtual threads:
Thread.Builder.OfVirtual virtualThreadBuilder = Thread.ofVirtual().name("worker-", 0);
Thread worker0 = virtualThreadBuilder.start(this::doSomethings);
worker0.join();Or using an executor:
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future
f = executor.submit(this::doSomethings);
f.get();
}Virtual threads are cheap and should not be pooled; each task gets its own virtual thread.
Best Practices
Write simple synchronous code with blocking APIs; virtual threads make blocking cheap.
Avoid mixing blocking code with asynchronous frameworks.
Do not share virtual threads via thread pools; use a new virtual‑thread executor for each set of tasks.
Do not cache expensive objects in ThreadLocal for virtual threads, because each task gets its own thread.
Limit pinning (e.g., synchronized blocks) or replace them with ReentrantLock to avoid long‑lasting carrier binding.
Use Semaphore to limit concurrency when accessing external services.
Performance Tests
A benchmark comparing virtual threads with platform thread pools (sizes 200, 500, 800, 1000) shows virtual threads completing 10 000 requests in ~330 ms, while platform threads take several seconds.
Web service tests with Spring Boot (Tomcat) and Spring WebFlux (Netty) under 3 000 concurrent users demonstrate that virtual threads achieve throughput comparable to WebFlux (≈5 200 req/s) with lower latency, without the complexity of reactive programming.
Conclusion
Java virtual threads simplify concurrent programming, increase throughput, and improve resource utilization. They are expected to become a standard practice for high‑performance server‑side Java applications.
DaTaobao Tech
Official account of DaTaobao Technology
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.