Site Search:

Transforming Java Concurrency with Virtual Threads (Project Loom)

Back> 

Transforming Java Concurrency with Virtual Threads (Project Loom)

With the arrival of virtual threads in Java 21, concurrent programming in Java has taken a major leap forward. Virtual threads are lightweight, memory-efficient threads managed by the JVM rather than the OS, allowing millions of concurrent tasks to run efficiently. This post explains how Java supports virtual threads, how they interact with the Java memory model, and how to modernize your existing code using Thread, ExecutorService, and Future.


What Are Virtual Threads?

Virtual threads (part of Project Loom) are lightweight user-mode threads. Unlike platform threads (which map 1:1 with OS threads), virtual threads are scheduled by the JVM and can be suspended or resumed without kernel intervention.

They support blocking operations without blocking the underlying kernel thread, which dramatically simplifies concurrent applications that traditionally relied on callbacks, thread pools, or complex async APIs.

In traditional Java, every Thread corresponds to a native OS thread. These threads are expensive to create (megabytes of memory per stack) and limited in number (~thousands max). In contrast, virtual threads are user-mode threads scheduled by the JVM, not the OS. The JVM can multiplex millions of virtual threads onto a small pool of carrier (platform) threads.


Threading Model Comparison:

Feature Platform Thread Virtual Thread
Mapped to OS ThreadYes (1:1)No (many:few)
Thread Creation CostHigh (kernel allocation)Low (JVM-managed)
ScalabilityThousandsMillions
Blocking I/OBlocks OS threadUnmounts, then resumes
Stack SizeFixed (~1MB)Small, growable, pausable

Memory Model

Virtual threads fully comply with Java’s memory model. Just like platform threads, they:

  • Have their own call stack
  • Share heap memory with other threads
  • Respect synchronized, volatile, and other concurrency constructs

However, virtual threads can be unmounted (paused) when blocked and remounted on another carrier thread later. This behavior is fully transparent to developers but allows far better scalability.




Migrating Traditional Concurrency to Virtual Threads

Replace new Thread() with Thread.startVirtualThread()

// Before
new Thread(() -> {
    handleRequest();
}).start();

// After
Thread.startVirtualThread(() -> {
    handleRequest();
});

Replace Fixed Thread Pools with Virtual Thread Executors

// Before
ExecutorService pool = Executors.newFixedThreadPool(10);

// After
ExecutorService pool = Executors.newVirtualThreadPerTaskExecutor();

With virtual threads, there’s no need to manually limit thread pool size in most applications — the JVM efficiently schedules virtual threads.

Futures Work Seamlessly

Future<Integer> result = pool.submit(() -> {
    return computeValue();
});
int value = result.get(); // blocking is fine with virtual threads

Native Blocking and Pinning

Certain operations can "pin" virtual threads to carrier threads:

  • Using native I/O or JNI that blocks
  • Holding locks for a long time
  • Waiting on synchronized blocks during blocking I/O

To avoid pinning, prefer:

  • java.nio (non-blocking channels)
  • Lock over synchronized if locks are held during I/O

Testing & Observability

Use jcmd or thread dumps to verify that virtual threads are being used:

jcmd <pid> Thread.dump

Look for thread names like VirtualThread[#] to confirm Loom is working as expected.


Migration Checklist

  • ✅ Replace new Thread(...) with Thread.startVirtualThread()
  • ✅ Switch from fixed thread pools to Executors.newVirtualThreadPerTaskExecutor()
  • ✅ Avoid native blocking APIs or wrap them in CompletableFuture.supplyAsync()
  • ✅ Review thread-local usage — avoid leaking references
  • ✅ Profile to avoid long-held locks in synchronized sections

Conclusion

Virtual threads modernize Java’s threading model, making high-concurrency programming simpler, safer, and more scalable. You can now write direct-style, blocking code that performs as well as complex async code — and converting existing thread-based applications is mostly straightforward.

Project Loom doesn't replace the need for understanding Java concurrency — but it reduces boilerplate and expands what’s possible in memory- and thread-efficient applications.

No comments:

Post a Comment