Unravel โบ Java concepts โบ Concurrency
Virtual threads
A virtual thread is a very cheap thread managed by the JVM instead of the operating system.
Learn these first
At a glance
Instead of a few expensive full-time employees who stand idle while waiting on the phone, you have an unlimited pool of temporary staff who are only paid (use a real worker) while actually talking.
- 1Thread.startVirtualThread(runnable), Thread.ofVirtual().name("job").start(r) or Executors.newVirtualThreadPerTaskExecutor() (one new virtual thread per task, never pooled)
- 2Code stays plain blocking code (inputStream.read(), JDBC, HttpClient.send)When it blocks, the JVM unmounts the virtual thread from its carrier (a small pool of platform threads) and lets another run.
- 3They are for I/O-bound concurrency (many waiting requests), not for CPU-heavy work, where only as many threads as cores help
- 4Spring Boot 3.2+ enables them with spring.threads.virtual.enabled=true
- Do not pool virtual threads; create one per task
- synchronized blocks that perform blocking I/O pinned the carrier thread in Java 21; prefer ReentrantLock there, or use a newer JDK where this is fixed
- A virtual thread's stack is stored on the heap as a continuation
import java.util.concurrent.*;import java.util.List;public class Fetcher { static void fetchAll(List<String> urls) throws Exception { try (ExecutorService exec = Executors.newVirtualThreadPerTaskExecutor()) { for (String u : urls) exec.submit(() -> { Thread.sleep(100); return u.length(); }); } } static Thread one(Runnable r) { return Thread.startVirtualThread(r); }}- line 5 uses newVirtualThreadPerTaskExecutor, the core of Virtual threads
- line 9 uses startVirtualThread, the core of Virtual threads
Read the full explanation (about 1 min)
A virtual thread is a very cheap thread managed by the JVM instead of the operating system. You can start a million of them, so a simple "one thread per request" blocking style scales again.
A mental model
Instead of a few expensive full-time employees who stand idle while waiting on the phone, you have an unlimited pool of temporary staff who are only paid (use a real worker) while actually talking.
How it works
Thread.startVirtualThread(runnable), Thread.ofVirtual().name("job").start(r) or Executors.newVirtualThreadPerTaskExecutor() (one new virtual thread per task, never pooled).
Code stays plain blocking code (inputStream.read(), JDBC, HttpClient.send). When it blocks, the JVM unmounts the virtual thread from its carrier (a small pool of platform threads) and lets another run.
They are for I/O-bound concurrency (many waiting requests), not for CPU-heavy work, where only as many threads as cores help.
Spring Boot 3.2+ enables them with spring.threads.virtual.enabled=true.
Going deeper
Do not pool virtual threads; create one per task. Limit concurrency of a scarce resource (database connections) with a Semaphore, not with the thread count.
synchronized blocks that perform blocking I/O pinned the carrier thread in Java 21; prefer ReentrantLock there, or use a newer JDK where this is fixed.
ThreadLocals still work but cost memory per virtual thread. StructuredTaskScope (preview) treats a group of subtasks as one unit with clean cancellation.
Under the hood
A virtual thread's stack is stored on the heap as a continuation. On a blocking call the continuation yields, its frames are copied away and the carrier thread (from a ForkJoinPool scheduler) takes the next virtual thread. Resuming copies the frames back.
Common mistakes
- Pooling virtual threads.
- Using them for CPU-bound loops.
- Holding a
synchronizedmonitor while doing blocking I/O. - Assuming ThreadLocal data is free.
How Unravel recognises it
When you paste code, Unravel looks for these signs of virtual threads. Spotting them yourself is a good habit when you read unfamiliar code:
- Types:
StructuredTaskScope - Method calls:
startVirtualThread()ofVirtual()newVirtualThreadPerTaskExecutor()isVirtual()newThreadPerTaskExecutor()unstarted()
Part of a learning path
- Concurrency beyond the basics ยท previous: ThreadLocal and scoped values ยท next: Fork/Join and divide and conquer
Related concepts
Frequently asked questions
What is Virtual threads?
A virtual thread is a very cheap thread managed by the JVM instead of the operating system. You can start a million of them, so a simple "one thread per request" blocking style scales again.
What are the most common mistakes with Virtual threads?
Pooling virtual threads. Using them for CPU-bound loops. Holding a synchronized monitor while doing blocking I/O. Assuming ThreadLocal data is free.
How can I see Virtual threads in real code?
Paste your own Java into Unravel and it will point out where virtual threads appears, explain how it works at the detail level you choose, and draw the flow of the code around it.