Java 21 introduced the lightweight virtual threads API. Every tutorial I encountered describes them as "as, or more scaleable" then the common platform threads.
I understand that virtual threads run on top of platform threads, but just from the user perspective I want to know if there are now any reasons left to directly use platform threads, for example for ExecutorServices.
I'm not interested in opions but concrete examples like insertion in the debate between Linked- and ArrayList.
synchronized in Java 21, 22, 23.)Semaphore (or some such).Virtual threads are very efficient with both memory and CPU. Common computer hardware can support over a million virtual threads.
Virtual threads should be your default choice rather than platform threads.
Virtual threads are contraindicated in any of these situations:
synchronized.If your task involves any of those situations, use platform threads.
(Occasional brief use of either synchronized or native code in a virtual thread is not a problem. Just avoid deadlocks, etc.)
The Project Loom team removed that limitation involving synchronized in Java 24 and later. See JEP 491: Synchronize Virtual Threads without Pinning.
Of course, “cheap” threads may perform expensive tasks. If your task involves a scarce or heavy resource, throttle your many virtual threads to make limited usage.
Throttling is commonly performed with Semaphore. Use try-finally to never lose a permit.
static final Semphore limitAccessToFooSemaphore = new Semaphore ( 7 ) ; // Specify number of permits. Or use `1` for a binary semaphore.
// Your task code, a `Runnable` or `Callable` implementation.
… do some inexpensive work
try
{
limitAccessToFooSemaphore.acquire() ; // Blocks until a permit becomes available.
… do expensive work
}
finally
{
limitAccessToFooSemaphore.release() ; // Return the permit to the semaphore’s pool.
}
… do more inexpensive work
Before virtual threads, some programmers got into the bad habit of using limited-size thread pools as a way of indirectly throttling access to scarce resources or limit expensive work. Better to manage those directly and explicitly via Semaphore or some such.
For more details, see the excellent video presentations at YouTube by Alan Bateman, Ron Pressler, or José Paumard.
See especially the latest by Alan Bateman where he explains that virtual threads are not “pixie dust”, correcting some misconceptions that have risen within the community.
And read JEP 444!
What does it mean to be "Using platform threads"
This should not mean "new Thread(...)".
Lets create an example. I am going to crawl a website, each request is going to require some IO. This io will consume a thread, but not use the CPU heavily.
executor.submit( () ->{
List<Runnable> next = crawlWebsite();
next.forEach( executor::submit );
});
crawWebsite is just a method that downloads the website parses it and finds the next URLs to visit.
If crawlWebsite gets stuck on the http request, then the os is free to prioritize another thread. If your executor has a fixed number of threads then they can all be stuck on IO? All of your processors are free, but you cannot start a new task because all of your threads are blocking.
Why not just create more threads?
Threads are "expensive". Some thread pools are built on the principle of creating more threads, like Executors.newCachedThreadPool. That works great, unless you are creating thousands of threads. Then the overhead becomes prohibitive.
That is what virtual threads are to address. Cheap actions that might block for a long time without consuming much cpu. Then you can submit the to Executors.newVirtualThreadPerTask. The threads are created used and forgotten. It gets around the finite threadpool problem, and it gets around the overhead of creating platform threads.
I'm not really convinced you shouldn't just use virtual threads instead of platform threads. One instance would be to limit the number of threads you use for a set of tasks.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With