In Java, traditional threads are mapped one - to - one to operating system (OS) threads. Each OS thread has its own stack space, which can be relatively large (usually in the order of megabytes). Creating a large number of traditional threads can quickly exhaust system resources such as memory and CPU, leading to performance degradation.
Virtual threads, introduced by Project Loom, are lightweight threads that are managed by the Java Virtual Machine (JVM) rather than the OS. They are multiplexed onto a smaller number of OS threads. Virtual threads have a much smaller stack size (in the order of kilobytes), which allows the JVM to create millions of virtual threads without consuming excessive resources.
Project Loom is an effort by Oracle to simplify concurrent programming in Java. It aims to make it easier to write high - throughput, low - latency applications by providing a new way to handle concurrency through virtual threads. Project Loom also includes other features such as structured concurrency, which helps in managing the lifecycle of concurrent tasks more effectively.
Here is a simple example of creating and starting a virtual thread:
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class VirtualThreadExample {
public static void main(String[] args) {
// Create a thread factory for virtual threads
ThreadFactory virtualThreadFactory = Executors.newVirtualThreadPerTaskExecutor();
// Create a virtual thread
Thread virtualThread = virtualThreadFactory.newThread(() -> {
System.out.println("Running in a virtual thread: " + Thread.currentThread());
});
// Start the virtual thread
virtualThread.start();
}
}
In this example, we first create a ThreadFactory
for virtual threads using Executors.newVirtualThreadPerTaskExecutor()
. Then we create a new virtual thread using the factory and start it.
Structured concurrency is another feature introduced by Project Loom. It helps in managing the lifecycle of concurrent tasks in a more organized way. Here is an example:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.StructuredTaskScope;
public class StructuredConcurrencyExample {
public static void main(String[] args) throws InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// Submit tasks to the scope
var task1 = scope.fork(() -> {
System.out.println("Task 1 running in thread: " + Thread.currentThread());
return "Result 1";
});
var task2 = scope.fork(() -> {
System.out.println("Task 2 running in thread: " + Thread.currentThread());
return "Result 2";
});
// Wait for all tasks to complete
scope.join().throwIfFailed();
// Get the results
String result1 = task1.get();
String result2 = task2.get();
System.out.println("Task 1 result: " + result1);
System.out.println("Task 2 result: " + result2);
}
}
}
In this example, we use StructuredTaskScope.ShutdownOnFailure
to manage the lifecycle of two concurrent tasks. The scope ensures that if any task fails, all other tasks are shut down.
Virtual threads are particularly well - suited for handling I/O - bound tasks. Since I/O operations often involve waiting for external resources, virtual threads can be efficiently multiplexed onto a smaller number of OS threads while waiting. Here is an example of handling an I/O - bound task with virtual threads:
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class IOBoundTaskExample {
public static void main(String[] args) {
ThreadFactory virtualThreadFactory = Executors.newVirtualThreadPerTaskExecutor();
Thread virtualThread = virtualThreadFactory.newThread(() -> {
try {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.example.com"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Response status code: " + response.statusCode());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
});
virtualThread.start();
}
}
While virtual threads are not as efficient as traditional threads for CPU - bound tasks, they can still be used to parallelize them in some cases. Here is an example of parallelizing a CPU - bound task:
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class CPUBoundTaskExample {
public static void main(String[] args) {
ThreadFactory virtualThreadFactory = Executors.newVirtualThreadPerTaskExecutor();
for (int i = 0; i < 10; i++) {
Thread virtualThread = virtualThreadFactory.newThread(() -> {
long sum = 0;
for (int j = 0; j < 1000000; j++) {
sum += j;
}
System.out.println("Sum: " + sum);
});
virtualThread.start();
}
}
}
As mentioned earlier, virtual threads shine in I/O - bound scenarios. If your application has a large number of I/O operations such as database queries, network requests, or file reads/writes, consider using virtual threads to handle these tasks.
For CPU - bound tasks, traditional threads may be a better choice as virtual threads have some overhead associated with them. If possible, use a fixed - size thread pool of traditional threads for CPU - bound tasks.
Structured concurrency helps in managing the lifecycle of concurrent tasks more effectively. Use StructuredTaskScope
to group related tasks and ensure that they are properly managed.
Virtual threads and Project Loom bring significant improvements to concurrent programming in Java. They address the limitations of traditional threads, especially in scenarios where handling a large number of concurrent tasks is required. By understanding the fundamental concepts, usage methods, common practices, and best practices, developers can make the most of virtual threads and write more efficient and scalable Java applications.