With Java 21, Virtual Threads have redefined how we approach concurrency, offering a lightweight and efficient way to handle parallel and asynchronous tasks. Unlike CompletableFuture
or ParallelStream
, Virtual Threads allow developers to write simple, synchronous-looking code while achieving the scalability of asynchronous solutions.
Key Features of Virtual Threads:
- Lightweight Concurrency: Virtual Threads are cheap to create and manage, enabling applications to handle thousands or even millions of concurrent tasks.
- Simpler Programming Model: Code written with Virtual Threads resembles traditional synchronous code, making it easier to understand and maintain.
- Automatic Thread Management: The JVM manages Virtual Threads' scheduling, eliminating the need to manually manage thread pools.
- Great for IO-Bound Tasks: Virtual Threads excel in handling IO-bound workloads, such as making multiple HTTP calls concurrently.
Example: Virtual Threads with HTTP Requests
Here’s how you can use Virtual Threads to fetch posts concurrently:
Example: Fetch Posts Using Virtual Threads
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.*;
public class VirtualThreadExample {
public static void main(String[] args) {
try (ExecutorService myExecutor = Executors.newVirtualThreadPerTaskExecutor()) {
// List of posts to process
List<Integer> posts = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20);
long start = System.nanoTime();
// Submit a task for each post
List<Future<Object>> futures = posts.stream()
.map(post -> myExecutor.submit(() -> {
getResponse("https://jsonplaceholder.typicode.com/posts/" + post);
return null; // Explicitly return null for Future<Void>
}))
.toList();
// Wait for all tasks to complete
for (Future<Object> future : futures) {
future.get(); // Ensures task completion
}
long duration = (System.nanoTime() - start) / 1_000_000;
System.out.printf("Processed %d posts in %d millis%n", posts.size(), duration);
System.out.println("Program Completed !!");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
public static void getResponse(String urlRest) {
try {
InputStream is = getInputStream(urlRest);
BufferedReader rd = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
StringBuilder response = new StringBuilder();
String line;
while ((line = rd.readLine()) != null) {
response.append(line);
}
rd.close();
System.out.println("Response: " + response.toString().trim());
} catch (Exception e) {
e.printStackTrace();
}
}
private static InputStream getInputStream(String urlRest) throws IOException {
URL url = new URL(urlRest);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Content-Language", "en-US");
connection.setConnectTimeout(60000);
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
return connection.getResponseCode() >= 400
? connection.getErrorStream()
: connection.getInputStream();
}
}
Performance and Simplicity
Output:
Processed 20 posts in 971 millis
Program Completed !!
Scalability: Virtual Threads scale seamlessly for handling a large number of requests.
Readability: The synchronous-looking code is easy to understand and maintain.
The ExecutorService created using Executors.newVirtualThreadPerTaskExecutor() in the try-with-resources block will automatically be shut down when the block is exited. This is because ExecutorService implements the AutoCloseable interface, and in a try-with-resources block, the close() method is automatically called.
if you're using the ExecutorService outside of a try-with-resources block, you must explicitly shut it down. This ensures that all threads are properly terminated and resources are released.
- No Thread Pool Management: Virtual Threads handle concurrency without the need for explicit thread pools or tuning.
- Efficient Resource Usage: They use far less memory compared to platform threads.
- Modern and Forward-Looking: As of Java 21, Virtual Threads are the future of Java concurrency, making them the ideal choice for most applications.