The Virtual Threads API is part of Project Loom in the Java platform. With Java 19, virtual threads became available as a preview feature, enabling the creation of lightweight threads that can run concurrently. They work similarly to traditional threads but are much cheaper in terms of memory and thread management because they are managed by the Java runtime, not the operating system. This makes it possible to scale the number of threads easily, even in the millions.
Here’s how you can use the Virtual Threads API:
1. Enable Virtual Threads
Virtual threads are available in Java 19+ as an incubating feature. To use them:
- Ensure that you’re using a compatible version of Java (Java 19 or later).
- Add the JVM flag
--enable-previewto enable preview features when running your program.
2. Creating Virtual Threads
Java provides the java.lang.Thread class and the Executors utility to work with virtual threads. For example:
Creating a Virtual Thread
You can create and start a virtual thread like this:
Thread.startVirtualThread(() -> {
System.out.println("This is a virtual thread!");
});
Using Virtual Threads with Executors
The Executors.newVirtualThreadPerTaskExecutor() method creates an ExecutorService that launches a new virtual thread for each task:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> System.out.println("Task on a virtual thread"));
executor.submit(() -> System.out.println("Another virtual thread task"));
}
3. Advantages of Virtual Threads
- Lightweight: Virtual threads are less resource-intensive because they use the Java runtime scheduler rather than the OS scheduler. Millions of threads can be created.
- Non-blocking: Blocking operations in virtual threads don’t block OS resources, making them very efficient for I/O-intensive workloads like web servers or concurrent network communication.
- Easier Scaling: They simplify concurrent programming by allowing you to continue using the familiar thread-per-task model without worrying about resource limits.
- Works with Existing Code: Virtual threads integrate well with existing Java APIs like
java.util.concurrent.
4. When to Use Virtual Threads
Virtual threads are ideal for:
- Concurrent I/O tasks like HTTP servers or database connections.
- High-concurrency environments where traditional threads might run out of OS resources.
- Migrating legacy multithreaded code to take advantage of better scalability.
5. Example: HTTP Server with Virtual Threads
Here’s a minimal example showcasing how to use virtual threads for handling multiple HTTP requests:
import java.net.ServerSocket;
import java.net.Socket;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.PrintWriter;
public class VirtualThreadHttpServer {
public static void main(String[] args) throws Exception {
try (var serverSocket = new ServerSocket(8080)) {
while (true) {
Socket clientSocket = serverSocket.accept();
Thread.startVirtualThread(() -> handleClient(clientSocket));
}
}
}
private static void handleClient(Socket clientSocket) {
try (var in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
var out = new PrintWriter(clientSocket.getOutputStream(), true)) {
out.println("Hello from the Virtual Thread server!");
String input;
while ((input = in.readLine()) != null) {
System.out.println("Received: " + input);
if ("exit".equalsIgnoreCase(input)) {
break;
}
out.println("You said: " + input);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
This makes use of virtual threads to handle each incoming socket connection, which scales efficiently for high-concurrency workloads.
6. Integration with Structured Concurrency
Virtual threads can be combined with structured concurrency (introduced in Java 21) for safer and more manageable multithreading. Structured concurrency allows parent threads to manage the lifecycle of child threads.
Example of Structured Concurrency:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class StructuredConcurrencyExample {
public static void main(String[] args) throws Exception {
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
var future1 = executor.submit(() -> {
Thread.sleep(500);
return "Task 1 completed";
});
var future2 = executor.submit(() -> {
Thread.sleep(300);
return "Task 2 completed";
});
// Wait for results
System.out.println(future1.get());
System.out.println(future2.get());
}
}
}
Keynotes:
- Virtual threads require no changes in application logic. Code written for traditional
Threadcan immediately benefit from using virtual threads. - They simplify thread management while maintaining excellent performance for non-blocking I/O operations.
Limitations:
- Virtual threads won’t improve performance for CPU-bound tasks; you still need to consider the number of logical CPUs in your system.
- JVM preview features need to be enabled since virtual threads are not yet finalized in the standard Java API. Check the latest Java release notes for updates.
