Contents

Use executorservice with virtual threads

Written by: David Vlijmincx

Introduction

With Java 21, we finally have virtual threads, which are a great new addition to the Java language. With the introduction of Virtual threads, we also get a new Executor that uses these virtual threads. The great thing is that we can also use a thread factory to use the existing executors services with virtual threads.

While this sounds promising, there is only one existing executor besides the new newVirtualThreadPerTaskExecutor that can benefit from virtual threads. Most Executors use only a single thread or pool their threads, you never want to do this with virtual threads. Pooling and using a single thread makes when using platform threads (normal threads, managed by the OS). With virtual threads, you want to make as many as you need when you need them. Never pool virtual threads.

In this post, we look at how to use the new newVirtualThreadPerTaskExecutor and how to use the newScheduledThreadPool with virtual threads.

Use newVirtualThreadPerTaskExecutor

newVirtualThreadPerTaskExecutor is the new ExecutorService that uses virtual threads by default. It will create a new virtual thread for each task that you submit. With Java 21, ExecutorService's now implement the autocloseable interface which means that you can use them inside a try-with-resource statement. The following example shows you how you can use this new newVirtualThreadPerTaskExecutor.

In the example, I create a new newVirtualThreadPerTaskExecutor and submit a single task that will print “Hello, world!” to the console.

1
2
3
4
5
6
7
try (ExecutorService vte = Executors.newVirtualThreadPerTaskExecutor()){
    vte.submit(()-> {
        System.out.println("Hello, world!");
    });

    vte.submit(()-> System.out.println("Hello, world!"));
};

You don't have to join on the thread. The great thing about using a try-with-resource statement is that it will only exit when all the virtual threads are done running.

Using scheduledExecutorService with virtual threads

You can also use the ScheduledExecutorService with virtual threads. To do this, you first need to create a factory that will create virtual threads for the ScheduledExecutorService to use. The ScheduledExecutorService will use the factory you gave it to create threads to run it scheduled tasks.

In the following example on line one, we create a factory that creates virtual threads. On line two we pass this factory as a parameter to the scheduledExecutorService. We also set the pool size to zero so the scheduledExecutorService doesn't pool threads.

Line 4 shows the creation of a Callable that will return a string value. On the last line, the Callable is scheduled to run 1 second later.

1
2
3
4
5
6
7
8
9
ThreadFactory factory = Thread.ofVirtual().factory();
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(0, factory);

Callable<String> scheduledCallable = () -> {
    System.out.println("Done");
    return "Done";
};

scheduledExecutorService.schedule(scheduledCallable, 1, TimeUnit.SECONDS);

The scheduledExecutorService will now run the scheduledCallable using a virtual thread.

Conclusion

We looked at how to executors with virtual threads. I did skip all the executors which didn't make sense to use with virtual threads. Because they either pooled threads or only used a single thread which you are not supposed to do with virtual threads.

Further reading