Discover Cloud Solutions with HostingerGet a special discount.

hostingerLearn More
Published on

Java Virtual Threads Introduction

Authors
  • avatar
    Name
    Luis Carbonel
    Twitter
Java Virtual Threads Introduction

Java Virtual Threads, also known as Project Loom, is a new feature introduced in Java 16 that promises to revolutionize the way we write concurrent programs in Java. In this blog post, we'll take a closer look at what virtual threads are, how they work, and what benefits they offer.

What are Virtual Threads?

Traditionally, Java threads have been implemented as operating system threads. This means that each Java thread is backed by an OS thread, which is a relatively heavy-weight resource. In contrast, virtual threads are lightweight threads that are managed by the JVM rather than the operating system.

Virtual threads are implemented using fibers, which are a user-space construct that allows for cooperative multitasking. Instead of preemptively switching between threads, as operating system threads do, fibers switch between themselves in a cooperative way, which eliminates the overhead of kernel-level context switches.

How do Virtual Threads Work?

Virtual threads are created using the java.lang.Thread class, just like regular threads. However, when a virtual thread is created, it is associated with a VirtualThreadGroup, which is a lightweight construct that represents a group of virtual threads. The VirtualThreadGroup manages the lifecycle of its associated virtual threads, and provides a way to handle exceptions that occur in those threads.

Virtual threads are scheduled by a new executor service called the ForkJoinPool#commonPool. This executor service is optimized for running tasks that are relatively short-lived and do not block. When a virtual thread is created, it is submitted to the common pool, which schedules it to run on a fiber. The fiber runs the virtual thread’s run() method, and when the method completes, the fiber is returned to the common pool for reuse.

What are the Benefits of Virtual Threads?

The main benefit of virtual threads is that they provide a more efficient way to write concurrent programs in Java. Because virtual threads are lightweight and managed by the JVM, they do not incur the overhead of operating system threads. This means that you can create many more virtual threads than you could create OS threads, which can lead to much better scalability and resource utilization.

Virtual threads also provide a more intuitive way to write concurrent programs. Because fibers switch between themselves cooperatively, there is no need to worry about issues like thread starvation or deadlock. This makes it easier to write correct and efficient concurrent code, even for developers who are not experts in concurrent programming.

Advantages of Virtual Threads Over Traditional Threads

  • Lightweight: Virtual threads are designed to be extremely lightweight. This characteristic allows them to be rapidly created and terminated, making them well-suited for executing short-lived, non-blocking tasks.

  • Scalable: Virtual threads exhibit scalability by providing the ability to quickly create and destroy threads. This scalability is especially valuable for efficiently managing short-duration tasks.

  • Resource Efficient: Virtual threads are highly resource-efficient, offering the capability to create and dismantle threads with minimal resource overhead. This efficiency is particularly beneficial for tasks with brief lifetimes.

  • User-Friendly: Virtual threads are user-friendly, providing a straightforward and accessible mechanism for creating and managing threads. This ease of use is advantageous when dealing with tasks that don't require complex thread management.

How to Use Virtual Threads?

Now we know what virtual threads are and how they work, let’s take a look at some use cases for virtual threads. To create a virtual thread, you can use the Thread.startVirtualThread method, which takes a Runnable or Supplier object as an argument.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VirtualThreadsExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newVirtualThreadExecutor();

        Runnable task = () -> {
            System.out.println("Running in a virtual thread");
            System.out.println("Thread name: " + Thread.currentThread().getName());
        };

        Thread.startVirtualThread(task);

        executor.shutdown();
    }
}

In this example, we create an ExecutorService using the newVirtualThreadExecutor method, which creates an executor that uses virtual threads to run tasks. We then create a Runnable object that prints a message and the name of the current thread. Finally, we start a virtual thread using the Thread.startVirtualThread method and pass in the Runnable object.

Note that virtual threads are automatically scheduled by the JVM, so you don’t have to explicitly manage thread scheduling or synchronization. Virtual threads also have a smaller memory footprint compared to regular threads, which can be important in applications that create a large number of threads.

How to combine tasks using Virtual Threads?

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VirtualThreadsExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newVirtualThreadExecutor();

        Runnable task1 = () -> {
            System.out.println("Running task 1 in a virtual thread");
            System.out.println("Thread name: " + Thread.currentThread().getName());
        };

        Runnable task2 = () -> {
            System.out.println("Running task 2 in a virtual thread");
            System.out.println("Thread name: " + Thread.currentThread().getName());
        };

        executor.submit(task1).thenRunAsync(task2, Thread::startVirtualThread);

        executor.shutdown();
    }
}

In this example, we use again an ExecutorService using the newVirtualThreadExecutor method, which creates an executor that uses virtual threads to run tasks. We then create two Runnable objects that print a message and the name of the current thread. Finally, we submit the first task to the executor, and then use the thenRunAsync method to start a virtual thread to run the second task.

How to handle exceptions using Virtual Threads?

import java.lang.Thread;
import java.lang.Runnable;

public class VirtualThreadsExample {
    public static void main(String[] args) {
        Runnable task = () -> {
            System.out.println("Running in a virtual thread");
            System.out.println("Thread name: " + Thread.currentThread().getName());
            throw new RuntimeException("Exception in virtual thread");
        };

        Thread virtualThread = Thread.startVirtualThread(task);
        virtualThread.setUncaughtExceptionHandler((thread, throwable) -> {
            System.out.println("Exception occurred: " + throwable.getMessage());
        });
    }
}

We set an uncaught exception handler using setUncaughtExceptionHandler on the virtual thread to handle exceptions that may occur during execution.

This approach ensures that exceptions thrown within the virtual thread are captured and handled appropriately. Remember that Java 17+ is required for this code to work with virtual threads.

Conclusion

Java Virtual Threads, or Project Loom, is a new feature in Java 16 that promises to make concurrent programming in Java more efficient and intuitive. By providing lightweight threads that are managed by the JVM, virtual threads eliminate the overhead of operating system threads and make it easier to write correct and efficient concurrent code. If you’re interested in learning more about virtual threads, I encourage you to check out the Project Loom website and give them a try in your own Java programs.