Working with Virtual Threads JAVA 21+
1. Understanding Virtual Threads
| Aspect | Detail |
| Type | Lightweight, JVM-managed threads |
| Cost | Few KB of memory each |
| Scale | Millions per JVM |
| API | Same Thread API as platform threads |
2. Creating Virtual Threads
// Direct
Thread vt = Thread.startVirtualThread(() -> doWork());
// Builder
Thread t = Thread.ofVirtual().name("worker-").start(() -> doWork());
// Factory
ThreadFactory tf = Thread.ofVirtual().factory();
| Form | Detail |
Thread.startVirtualThread(r) | One-shot |
Thread.ofVirtual().start(r) | Builder |
Thread.ofPlatform()... | Explicit platform |
3. Using Thread.ofVirtual() Builder
| Method | Detail |
name(prefix, counter) | Naming |
uncaughtExceptionHandler(h) | Error handler |
factory() | Returns ThreadFactory |
start(r) / unstarted(r) | Build |
| Aspect | Virtual | Platform |
| OS thread | No (mounted on carrier) | Yes (1:1) |
| Stack size | Growable, small | Fixed, ~1MB |
| Best for | I/O blocking | CPU-bound |
| Pooling | Anti-pattern | Use ThreadPoolExecutor |
5. Using Virtual Threads with Executors
| API | Detail |
Executors.newVirtualThreadPerTaskExecutor() | One vthread per task |
| No pooling | VThreads are cheap; create per task |
6. Understanding Carrier Threads
| Aspect | Detail |
| Pool | Default ForkJoinPool sized to CPUs |
| Mount/unmount | VThread mounts to carrier when running |
| Unmount on block | Frees carrier for other vthreads |
7. Avoiding Pinning
| Cause | Effect |
synchronized block during blocking I/O | VThread pinned, carrier blocked |
| Native method (JNI) | Pinned |
| Detect | -Djdk.tracePinnedThreads=full |
| Fix | Replace synchronized with ReentrantLock |
Warning: A virtual thread pinned during a blocking operation prevents its carrier from running other virtual threads.
8. Using Virtual Threads with HTTP Client
| Pattern | Detail |
| Sync API | Use send() with vthread per request |
| Async API | Less needed with vthreads |
| Step | Detail |
| Identify I/O-bound code | Best candidate |
| Replace pools | Use newVirtualThreadPerTaskExecutor |
| Audit synchronized | Convert to ReentrantLock if pinning |
| Don't migrate CPU-bound | Keep platform threads |
| Practice | Detail |
| Don't pool vthreads | Create per task |
| Avoid ThreadLocal heavy use | Allocates per vthread; use ScopedValues instead |
| Tune carrier pool | -Djdk.virtualThreadScheduler.parallelism=N |