Introduction
For developers entrenched in the complexities of concurrent programming, virtual threads represent more than just another update. It's a potential game-changer, a practical solution to the long-standing challenges of managing multiple tasks efficiently. With the promise of enhanced scalability and performance, these virtual threads are poised to simplify the intricacies of handling concurrent operations, enabling developers to streamline their workflows and optimize resource utilization. In this blog post, we'll take a pragmatic dive into the core functionalities and application of Java 21's virtual threads, examining their real-world implications and the ways in which they can revolutionize the development process. Get ready to uncover a powerful tool that's not just theoretical, but one that holds the promise of significantly improving the practical aspects of your coding endeavors.
What are virtual threads exactly?
Virtual threads represent a significant shift in how the Java Virtual Machine (JVM) handles threads. Unlike traditional threads (called Platform or Native threads), which are mapped directly to operating system (OS) threads, virtual threads are managed by the JVM itself. They are lightweight, highly scalable, and designed to enable more efficient use of system resources.
These virtual threads operate within a shared pool of underlying OS threads, allowing the JVM to manage them dynamically, based on the available resources and the workload. This approach minimizes the overhead associated with creating and managing OS threads, making it possible to handle a much larger number of concurrent tasks without overburdening the system.
By decoupling the association between Java threads and OS threads, virtual threads provide a more flexible and resource-efficient mechanism for managing concurrency in Java applications. This feature enables developers to create a significantly higher number of threads without the performance penalties typically associated with the traditional thread model. Additionally, virtual threads facilitate smoother integration with existing codebases and frameworks, making it easier to optimize the performance of concurrent applications without significant code changes.
How to use virtual threads
The following is a code example on how to use virtual threads using the Thread
class.
Thread.startVirtualThread(() -> {
System.out.println("Running virtual thread: " + Thread.currentThread().getName());
});
// or
Thread.ofVirtual().start(() -> System.out.println("Running virtual thread: " + Thread.currentThread().getName());
thread.join(); // for allowing you to see the message as it waits for the thread to terminate, before the main thread terminates
And the following is a code example on how to use virtual threads using the ExecutorService
interface.
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
executor.submit(() -> {
System.out.println("Running virtual thread with ExecutorService: " + Thread.currentThread().getName());
});
The last example is the recommended approach due to higher level abstraction and the fact that you don't have to worry about thread management yourself as ExecutorService
will reuse threads for multiple tasks, ultimately reducing overhead on creation and destruction of said threads.
Virtual Threads under the hood
Internally, a specialized pool of platform threads, which is essentially a customized fork-join pool, is employed to execute virtual threads. These virtual threads are tethered to specific platform threads from the pool, utilizing them for task execution.
Consider a scenario where your task involves a blocking I/O operation, such as a database request to retrieve user data. It's crucial to prevent the blocking of a platform thread. In such instances, the virtual thread identifies the blocking I/O operation and unmounts itself from the platform thread it's running on. It accomplishes this by moving its context/stack to the heap memory.
More specifically, this detachment is done by a specialized object known as a Continuation
, which utilizes its run()
method to manage the execution of the virtual thread and, consequently, your task. When a blocking call occurs, a call to Continuation.yield()
initiates the process of unmounting the virtual thread from the platform thread and copying its stack to the heap memory. This process can be seen in the following animation:
After the response data is available, the operating system handler responsible for monitoring the data triggers a signal that invokes Continuation.run()
. This action involves retrieving the stack of the virtual thread from the heap memory and placing it in the wait list of the original platform thread within the fork-join pool. This can be seen below:
Furthermore, in situations where a thread is busy, and an alternative thread is accessible, the latter may 'steal' the task from the former. This phenomenon allows for the seamless transition of tasks between different platform threads when a thread is busy.
Conclusion
As we've explored throughout this post, the benefits of virtual threads extend beyond mere optimization; they represent a fundamental shift in the way developers approach and implement concurrent operations in Java applications. With the ability to seamlessly integrate with existing codebases and frameworks, virtual threads empower developers to harness the full potential of concurrent programming, enabling the creation of highly responsive, resource-efficient, and scalable applications.
As developers continue to leverage this groundbreaking feature, it is evident that virtual threads will play a pivotal role in shaping the future of high-performance, concurrent applications, ushering in a new era of efficiency and scalability in the world of Java programming.
Useful references
2 minute video that explains virtual threads: https://www.youtube.com/watch?v=bOnIYy3Y5OA
Deep dive video on virtual threads:
https://www.youtube.com/watch?v=5E0LU85EnTI
Oracle documentation on virtual threads:
https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html#GUID-DC4306FC-D6C1-4BCC-AECE-48C32C1A8DAA
Top comments (0)