Contents

Pitfalls you encounter with virtual threads

Introduction

Virtual threads are new and a great, if not better alternative to Threads for specific workloads. However, you need to keep some things in mind while implementing or working with them. In this article, I will go over some pitfalls you may encounter while you are working with virtual threads.

Don't pool virtual threads

When we created a thread in a previous version of Java, it also created a thread managed and scheduled by the operating system (OS). These threads that the OS manages are called platform threads. To create a platform thread, you need to make a system call, and these are expensive. Because they are so expensive to make, it is beneficial to reuse them instead of creating a new one every time you need one.

Virtual Threads, on the other hand, are cheap to make, and you can have many of them simultaneously. They are cheap because no system call is needed to create them. Therefore, there is no need to pool them. You can keep producing them and throw them away when they are no longer required.

Limiting access to a resource (with a pool)

You may have a resource like a database or other service that can only handle a certain number of threads simultaneously. With platform threads, you could create a pool that holds the same number of threads as your resource could take.

Virtual threads are not meant to be pooled, so we cant use a pool to limit access to a resource. However, this creates the opportunity to improve the quality of your code by making it more explicit. Instead of a pool, we can use a semaphore or another type of lock to prevent too many threads from accessing the recourse at the same time.

Replace an existing Executor with one for virtual threads

Virtual threads are still very new. Libraries may not have had the chance to experiment and adopt the usage of virtual threads. It is smart to start small and see what impact it has on your application. So don't blindly replace threads with virtual threads in your application.

The reason is that libraries or your application are written with the idea that threads are expensive to create and pool them. Pooling is something you don't ever have to do with virtual threads. A simple way to start is to replace an ExecutorService that currently uses a pool with Executors.newVirtualThreadPerTaskExecutor() that creates a new virtual thread for each task.

Managing the lifetime of threads yourself (most of the time)

Project Loom did not only introduce Virtual threads, but it also gave us structured concurrency. The idea behind structured concurrency is to make the lifetime of a thread work the same as code blocks in structured programming. For example, in a structured programming language like Java, If you call method B inside method A, then method B must be finished before you can exit method A. The lifetime of method B can’t exceed that of method A.

With Structured concurrency, we want the same kind of rules as with structured programming. When you create virtual thread X inside virtual thread Y, the lifetime of thread X can’t exceed that of thread Y. Structured concurrency makes working and thinking about threads much easier. The crux of the pattern is to avoid fire and forget concurrency.

If you want to know more about structured concurrency read this post.

Threads are basically free now

The most important thing to remember is that virtual threads are cheap to create, and you don't want to pool them. This is a shift in mindset you have to make when you work with this new type of thread.

You don't have to learn anything new to use virtual threads

Virtual threads don't require you to learn a whole new library. The language did not change. Only a couple of new methods were added. In the following example, you see the most important ones.

1
2
3
4
5
6
7
8
// Create a virtual thread for a given task.
Thread ofVirtualThread = Thread.ofVirtual().start(task);

// check if a thread is a virtual thread
ofVirtualThread.isVirtual()
        
// A new method to create an executorService for virtual threads 
ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();

In the end, there are not a lot of new methods or APIs you have to learn. There are, however, some habits or best practices you learned that you have to forget while working with virtual threads.

Conclusion

In this article, we looked at six pitfalls that you might encounter while working with virtual threads. Virtual threads are still in preview at the time of writing, so they are subject to change. If you find this helpful, have comments, feedback or encountered another pitfall, feel free to contact me.

Further reading

More about virtual threads in Java:

Have a question or idea?

Thank you for reading my post. Have any questions about this topic or an idea for a new topic? Please reach out to me on Twitter or send me an email.

If you want to read more about Java when I write about it – you can follow me on Twitter.