Java 21–Virtual Threads
Virtual threads in Java were introduced to address certain limitations and challenges associated with traditional operating system (OS) threads. To understand the need for virtual threads, let’s delve into how threads work internally in Java and why there’s a concern with using OS threads.
In Java, when you create a thread, it is directly tied to an OS thread. This means that every Java thread consumes an OS thread, and this connection has some implications:
1. Resource Limitations: Just like file handles, ports, and drivers, the operating system has limitations on the number of threads it can effectively handle. If an application creates too many threads, it can lead to resource exhaustion.
2. Scalability: When applications use a significant number of OS threads, they can struggle to scale efficiently. This can be a problem in scenarios where you need to handle a large number of concurrent tasks, like in web servers.
Now, you might be wondering, who actually uses threads, and why should you care?
Well, many applications, especially web servers like those built with Spring Boot, rely heavily on threads for concurrent task handling. These threads are responsible for processing incoming requests, and as you rightly pointed out, it can be challenging to keep track of what each thread is doing.
Consider a scenario where your Spring Boot application is serving numerous concurrent requests. These requests could be doing various things like making database queries, waiting for responses from web services, or reading large files in loops. The issue is that each thread assigned to a request is exclusively focused on that request, making it difficult to efficiently utilize available resources.
For Example , Consider the below Scenario
We will create 5000 Threads to call a method named handleRequest
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 5000; i++) {
// create a thread
Thread thread = new Thread(() -> {
handleRequest();
});
// start the thread
thread.start();
// add the thread to the list
threads.add(thread);
System.out.println("Thread " + thread.threadId()+ " started");
}
// wait for all the threads to finish
for (Thread thread : threads) {
thread.join();
}
While each thread
- print a message and wait for 3 seconds
void handleRequest() {
try {
System.out.println("Handling request on Thread " + Thread.currentThread().threadId() + " started");
Thread.sleep(3000);
System.out.println("Handling request on Thread " + Thread.currentThread().threadId() + " completed");
}
}
The expectation would be all might work well , yes it is , But when it runs
Exception in thread "main" java.lang.RuntimeException:
java.lang.OutOfMemoryError: unable to create native thread:
possibly out of memory or process/resource limits reached
at virtualThread.ClassicThread.startClassicalThreads(ClassicThread.java:69)
at virtualThread.ClassicThread.main(ClassicThread.java:34)
You can see OutOfMemoryError , because of Resource limits reached.
Lets migrate the same code , With Only Change to use Virtual Thread
Instead of this
Thread thread = new Thread(() -> {
handleRequest();
});
Use this one
Thread thread = Thread.ofVirtual().unstarted(() -> {
handleRequest();
});
Thread.ofVirtual() is to create a virtual Thread and we are calling unstarted method with a callback , so that Thread would be just created and we will start it manually
Run the program
Now you will see the following output
All 5000 threads completed successfully
Since its working well , why cant we increase the loop from 500 to 5,00,000
for (int i = 0; i < 500000; i++) {
// create a thread
Thread thread = Thread.ofVirtual().unstarted(() -> {
handleRequest();
});
// start the thread
thread.start();
//...........
Run the program
The Output would be
All 500000 threads completed successfully
Here’s a recap of the key advantages of virtual threads:
1. Resource Efficiency: Virtual threads are not directly tied to OS threads, which means they don’t consume OS resources in the same way traditional threads do. This makes them highly efficient, as demonstrated by your ability to run 500,000 virtual threads without running into resource limitations.
2. Scalability: Virtual threads are lightweight, making it much easier to scale your application without worrying about the overhead of managing a massive number of OS threads. This is especially important in scenarios like web servers, where handling a large number of concurrent requests efficiently is critical.
3. Reduced Overhead: Virtual threads have lower overhead compared to OS threads, as they are managed by the Java Virtual Machine (JVM). This leads to improved performance and a reduced risk of running into issues like OutOfMemoryErrors.
So if your webserver took 3 second to serve a request, Isnt that it would be cool to serve 500,000 requests without spinning up a new server ?
Full Source Code: