In this brief article, I will cover the overall idea of virtual threads in Java.
“Virtual threads are lightweight threads that dramatically reduce the effort of writing, maintaining, and observing high-throughput concurrent applications.” – JEP 444. [JEP stands for JDK Enhancement Proposal]
Virtual threads were first introduced in JDK 19 as a preview feature under the JEP 425. They continue their journey as preview feature (under JEP 436) in JDK 20. Finally, JEP 444 proposes to finalize the virtual threads in JDK 21.
First Things First
Before understanding and using virtual threads, you must be clear of the below important points:
- If your tasks spend most of the time blocked (e.g., waiting for IO operations to complete), you should consider using virtual threads.
- Virtual threads are not the right choice for long-running CPU intensive tasks.
- Don’t try to use virtual threads to process large data sets in parallel. Instead, consider Java Stream API.
- Traditional implementations of threads in Java platform will remain available. Based on your requirements, you need to choose between virtual threads and platform threads.
Before Virtual Threads
We deal with platform threads. One platform thread is typically mapped to one kernel thread scheduled by the operating system. Creating a kernel thread (aka OS thread) is costly. We cannot have too many of them.
If server application allocates a platform thread for each request (thread-per-request style), it won’t scale well. Because, OS threads are limited resources. Moreover, pooling of threads won’t help here. Because, pooling doesn’t help increase the number of OS threads.
To improve scalability, you can consider thread-sharing style instead of thread-per-request style. Here, one request is handled by multiple threads from start to finish. Request does not hold on to a platform thread when it waits or is blocked. So, platform thread gets the opportunity to serve other requests instead of being blocked.
In thread-sharing, you need to follow asynchronous programming style. Since, request is not being handled by a dedicated platform thread, you need to break your request handling logic into small stages, where each stage might execute on a different thread. Every platform thread runs stages belonging to different requests in an interleaved fashion. In asynchronous programming, scalability improves. But the biggest problem with asynchronous is debugging the code, for multiple threads are involved. Moreover, asynchronous code is hard to understand. We need better solution to improve scalability without compromising readability and debugging ease. Here comes virtual threads.
What Are Virtual Threads?
At Operating System level, we have kernel threads. At the JVM level, when we create a thread (more accurately, platform thread), that platform thread is mapped to one kernel thread internally. But when we deal with virtual threads, JVM maps a large number of virtual threads to a small number of platform threads. These platform threads are used as carrier threads.
Virtual threads are a lightweight implementation of threads that is provided by the JDK rather than the OS.
“A virtual thread is an instance of java.lang.Thread
that is not tied to a particular OS thread. A platform thread, by contrast, is an instance of java.lang.Thread
implemented in the traditional way, as a thin wrapper around an OS thread.” ~ JEP 444
The below code demonstrates (at least one way) how to create virtual and platform thread in Java.
public class VirtualTask implements Runnable {
@Override
public void run() {
System.out.println("Virtual Task is completed");
}
}
public class PlatformTask implements Runnable {
@Override
public void run() {
System.out.println("Platform Task is completed");
}
}
public class VirtualThreadDemo {
public static void main(String[] args) {
// Start a virtual thread to run a task
Thread vt = Thread.ofVirtual().start(new VirtualTask());
System.out.println("Virtual Thread Name: " + vt.getName());
// Start a platform thread to run a task
Thread pt = Thread.ofPlatform().daemon().start(new PlatformTask());
System.out.println("Platform Thread Name: " + pt.getName());
}
}
Conclusion
- Application code can run in a virtual thread for the entire duration of a request.
- When virtual thread is performing calculations for the given request, it is mounted on a carrier thread (basically a platform thread).
- When virtual thread faces any blocking / waiting operations, it gets unmounted (though exceptions are there) from the carrier thread and JVM suspends the virtual thread until it can be resumed later.
- We achieve the same scalability as asynchronous programming, but the process is very transparent.
- Many virtual threads can run their code on the same kernel thread, effectively sharing it.
That’s all for this article.
If you find any significant errors or want to give me some feedback, feel free to contact me at maliksanjoykumar[@]gmail.com.
Sanjoy Kumar Malik is an experienced software architect and technologist. He is passionate about Cloud Computing, Software Architecture, and System Design. Apart from technology and software, he is an avid LinkedIn networker. You can join his 5.5+ lacs supporters on LinkedIn.