Project loom is still in preview, meaning that the API may change at any time. If you want to try the examples yourself they are made with Early-access build 19-loom+4-115 (2022/2/13).
Introducing virtual threads
A thread in Java is just a small wrapper around a thread that is managed and scheduled by the OS. Project Loom adds a new type of thread to Java called a virtual thread, and these are managed and scheduled by the JVM.
To create a platform thread (a thread managed by the OS), you need to make a system call, and these are expensive. To create a virtual thread, you don't have to make any system call, making these threads cheap to make when you need them. These virtual threads run on a carrier thread. Behind the scenes, the JVM created a few platform threads for the virtual threads to run on. Since we are free of system calls and context switches, we can run thousands of virtual threads on just a few platform threads.
Creating virtual threads
The easiest way to create a virtual thread is by using the
Thread class. With Loom, we get a new builder method and factory method to create virtual threads.
The example first shows us how to create a platform thread, followed by an example of a virtual thread. Virtual and platform threads both take a
Runnable as a parameter and return an instance of a thread. Also, starting a virtual thread is the same as we are used to doing with platform threads by calling the
Creating virtual threads with the Concurrency API
Loom also added a new executor to the Concurrency API to create new virtual threads. The new
returns an executor that implements the
ExecutorService interface just as the other executors do. Let's start with an example of using the
Executors.newVirtualThreadPerTaskExecutor() method to obtain an
ExecutorService that uses virtual threads.
As you see, it doesn't look different from the existing executors. In this example we use the
Executors.newVirtualThreadPerTaskExecutor() to create a executorService. This virtual thread executor executes each task on a new virtual thread. The number of threads created by the VirtualThreadPerTaskExecutor is unbounded.
Can I use existing executors?
The short answer is yes; you can use the existing executors with virtual threads by supplying them with a virtual thread factory. Keep in mind that those executors were created to pool threads because platform threads are expensive to create. Using an executor that pools threads in combination with virtual threads probably works, but it kind of misses the point of virtual threads. You don't have to pool them because they are cheap to create.
On the first line, we create a virtual thread factory that will handle the thread creation for the executor. Next, we call the
new method for each executor and supply it the factory that we just created. Notice that calling
newThreadPerTaskExecutor with a virtual thread factory is the same as calling
When we use CompletableFuture we try to chain our actions as much as possible before we call
get, because calling it would block the thread.
With virtual threads calling
get won't block the thread anymore. Without the penalty for using
get you can use it whenever you like and don't have to write asynchronous code. This makes writing and reading Java code a lot easier.
With Threads being cheap to create, project Loom also brings structured concurrency to Java. With structured concurrency, you bind the lifetime of a thread to a code block. Inside your code block, you create the threads you need and leave the block when all the threads are finished or stopped.
Try-with-resources statements can use an ExecutorService because project Loom extended Executors with the AutoCloseable interface.
try, we submit all the tasks we need to get done, and we leave the
try once the threads finish. The output in the console will look like this:
The second dashed line will never be printed between the numbers because that thread waits for the try-with-resources to finish.
In this post, we looked at what Loom will possibly bring to a future version of Java. The project is still in preview, and the APIs can change before we see it in production. But it's nice to explore the new APIs and see what performance improvements it already gives us.