Virtual Threads

Virtual threads are lightweight threads that reduce the effort of writing, maintaining, and debugging high-throughput concurrent applications. They are introduced in Java 19 as a preview feature and can be enabled by using --enable-preview command line option. This article will discuss virtual threads in detail.

Green Threads

Platform Thread

Virtual Thread

1 - Mounting and Unmounting Virtual Threads

2 - Pinning a virtual thread

3 - Little's Law

T = N / d

When to use virtual threads?

Code examples of virtual threads

1 - Spawning a single virtual thread

try {
    var builder = Thread.ofVirtual().name("MyThread");
    Runnable task = () -> {
        System.out.println("Running virtual thread: "
            + Thread.currentThread().getName());
    };
    var thread = builder.start(task);
    System.out.println("Started virtual thread: " + thread.getName());
    // wait for the thread to terminate
    thread.join();
} 
catch (InterruptedException e) {
    System.err.println("Caught InterruptedException");
}

Output:

Started virtual thread: MyThread
Running virtual thread: MyThread

2 - Spawning multiple virtual threads using executor

In this code, we are computing sum of 0 till 100 on each of the 8,000 virtual threads and then later summing up the results:

var executor = Executors.newVirtualThreadPerTaskExecutor();

Consumer<Long> sleep = (timeInMs) -> {
	try {
		TimeUnit.MILLISECONDS.sleep(timeInMs);
	}
	catch(InterruptedException ex) {
		System.out.println("InterruptedException occurred.");
	}
};

var futureList =
	IntStream.range(0, 8_000)
	.boxed()
	.map(idx ->
		executor.submit(() -> {
			var sumTill100 =
				IntStream.range(0, 100)
				.map(num -> {
					sleep.accept(5L);
					return num;
				})
				.reduce((a, b) -> a + b).orElse(0);

			System.out.println("Sum till 100 computed by Thread ID " + Thread.currentThread().threadId() + " is " + sumTill100);

			return sumTill100;
		})
	).collect(Collectors.toList());
		
sleep.accept(1000L);    

var sum =
	futureList.stream()
	.map(future -> {
		try {
			return future.get();
		}
		catch (InterruptedException | ExecutionException ex) {
			System.err.println("Caught InterruptedException or ExecutionException");
			return 0;
		}
	})
	.reduce((a, b) -> a + b).orElse(0);

System.out.println("The value of sum (should be 39600000): " + sum);

executor.shutdown();

Output:

Sum till 100 computed by Thread ID 7349 is 4950
Sum till 100 computed by Thread ID 7355 is 4950
...
Sum till 100 computed by Thread ID 8021 is 4950
The value of sum (should be 39600000): 39600000

Best practices for using virtual threads

1 - Do not pool virtual threads

2 - Use semaphores for limited access

3 - Avoid pinning virtual threads to their carrier

4 - Use only when you want to increase throughput

© 2022 Sumeet Das