DEV Community

Said Olano
Said Olano

Posted on

Understanding Java Virtual Threads: Lightweight Concurrency in Java 21

Understanding Java Virtual Threads: Lightweight Concurrency in Java 21

Java 21 introduced one of the most significant changes to the Java concurrency model in years: virtual threads, delivered as part of Project Loom. In this post, we'll explore what virtual threads are, why they matter, and how to use them effectively.

The Problem with Platform Threads

Traditionally, every java.lang.Thread in Java was backed by an operating system thread, often called a platform thread. These are expensive:

  • Each thread consumes around 1MB of stack memory.
  • The OS scheduler limits how many can run efficiently.
  • Blocking a thread on I/O wastes a valuable resource.

This forced developers toward complex asynchronous programming models (callbacks, reactive streams, CompletableFuture chains) to achieve scalability.

Enter Virtual Threads

Virtual threads are lightweight threads managed by the JVM rather than the OS. Thousands—even millions—can run concurrently because they are cheap to create and block.

java
// Creating a virtual thread
Thread.startVirtualThread(() -> {
System.out.println("Running in a virtual thread!");
});

When a virtual thread blocks on I/O, the JVM unmounts it from its carrier (platform) thread, freeing that carrier to run other virtual threads.

Using an Executor

The recommended way to work with virtual threads is through the new executor:

java
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
}

This code spawns 10,000 concurrent tasks without exhausting system resources—something impractical with platform threads.

Key Benefits

  1. Simpler code: Write straightforward, blocking-style code that scales.
  2. High throughput: Ideal for I/O-bound workloads like web servers.
  3. Backward compatible: Virtual threads implement the same Thread API.

When NOT to Use Virtual Threads

  • CPU-bound tasks: Virtual threads don't add parallelism for compute-heavy work.
  • Thread pooling: Don't pool virtual threads; create a new one per task.
  • ThreadLocal misuse: Heavy ThreadLocal usage can offset memory savings.

Conclusion

Virtual threads make the classic thread-per-request model viable again, eliminating much of the complexity of reactive programming while delivering excellent scalability. If you're building I/O-heavy applications on Java 21+, virtual threads deserve a place in your toolbox.

Top comments (0)