Working with Virtual Threads (Java 21+)
1. Creating Virtual Threads (Thread.startVirtualThread)
| API | Use |
|---|---|
Thread.startVirtualThread(r) | Quick start |
Thread.ofVirtual().start(r) | Builder, named |
Thread.ofVirtual().unstarted(r) | Manual start |
2. Using Executors.newVirtualThreadPerTaskExecutor
| Aspect | Detail |
|---|---|
| Per task | One virtual thread per submitted task |
| No pooling | Virtual threads are cheap |
| AutoCloseable | Use try-with-resources |
Example: Per-task executor
try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<String>> results = exec.invokeAll(tasks);
}
3. Using Thread.ofVirtual Builder
| Builder Method | Use |
|---|---|
name(prefix, counter) | Diagnostic naming |
uncaughtExceptionHandler(h) | Error handling |
inheritInheritableThreadLocals(b) | TL inheritance |
factory() | Get ThreadFactory |
4. Understanding Virtual Thread Scheduling
Virtual Thread (cheap, 100Ks possible)
↕ mount/unmount
Carrier Thread (platform, ForkJoinPool)
↕
Operating System
| Aspect | Detail |
|---|---|
| Scheduler | Dedicated ForkJoinPool |
| Mount | VT runs on a carrier |
| Unmount | On blocking I/O — carrier free |
5. Avoiding Pinning
| Cause | Mitigation |
|---|---|
synchronized blocking | Use ReentrantLock instead |
| JNI/native call | Unavoidable — carrier blocked |
| Detect | -Djdk.tracePinnedThreads=full |
6. Using Scoped Values
| API | Use |
|---|---|
ScopedValue.newInstance() | Define |
ScopedValue.where(sv, val).run(r) | Set in scope |
sv.get() | Read in scope |
| vs ThreadLocal | Immutable, scope-bound, no leak |
7. Understanding Performance Benefits
| Metric | Virtual | Platform |
|---|---|---|
| Memory | ~few KB stack | ~1 MB stack |
| Creation | Microseconds | Milliseconds |
| Max threads | Millions | Thousands |
| Best for | Blocking I/O concurrency | CPU-bound |
8. Migrating from Platform Threads
| Replace | With |
|---|---|
| Fixed thread pool | Virtual-thread-per-task executor |
synchronized | ReentrantLock |
| ThreadLocal heavy use | ScopedValue |
| Caching expensive objects | No need — VTs are cheap |
9. Debugging Virtual Threads
| Tool | Use |
|---|---|
| Thread dump (jcmd) | Includes VT stacks |
JFR VirtualThreadStart/End | Lifecycle events |
JFR VirtualThreadPinned | Pinning detector |
| IDE | Debugger supports VTs |
10. Understanding Carrier Threads
| Aspect | Detail |
|---|---|
| Type | Platform thread in dedicated FJP |
| Count | Default = available cores |
| Tune | -Djdk.virtualThreadScheduler.parallelism=N |
11. Using Virtual Threads with Blocking I/O
| Operation | Behavior |
|---|---|
| Socket / file I/O | Unmounts (carrier free) |
| Park (LockSupport) | Unmounts |
| Sleep | Unmounts |
synchronized | Pins (until JEP fix) |
12. Understanding Structured Concurrency Integration
| Combo | Detail |
|---|---|
| VT + StructuredTaskScope | Idiomatic concurrent code |
| Cancellation | Scope shutdown propagates |
| Error handling | ShutdownOnFailure aggregates |