Working with Virtual Threads JAVA 21+

1. Understanding Virtual Threads

AspectDetail
TypeLightweight, JVM-managed threads
CostFew KB of memory each
ScaleMillions per JVM
APISame Thread API as platform threads

2. Creating Virtual Threads

Example: Creation forms

// Direct
Thread vt = Thread.startVirtualThread(() -> doWork());

// Builder
Thread t = Thread.ofVirtual().name("worker-").start(() -> doWork());

// Factory
ThreadFactory tf = Thread.ofVirtual().factory();
FormDetail
Thread.startVirtualThread(r)One-shot
Thread.ofVirtual().start(r)Builder
Thread.ofPlatform()...Explicit platform

3. Using Thread.ofVirtual() Builder

MethodDetail
name(prefix, counter)Naming
uncaughtExceptionHandler(h)Error handler
factory()Returns ThreadFactory
start(r) / unstarted(r)Build

4. Comparing Virtual vs Platform Threads

AspectVirtualPlatform
OS threadNo (mounted on carrier)Yes (1:1)
Stack sizeGrowable, smallFixed, ~1MB
Best forI/O blockingCPU-bound
PoolingAnti-patternUse ThreadPoolExecutor

5. Using Virtual Threads with Executors

APIDetail
Executors.newVirtualThreadPerTaskExecutor()One vthread per task
No poolingVThreads are cheap; create per task

6. Understanding Carrier Threads

AspectDetail
PoolDefault ForkJoinPool sized to CPUs
Mount/unmountVThread mounts to carrier when running
Unmount on blockFrees carrier for other vthreads

7. Avoiding Pinning

CauseEffect
synchronized block during blocking I/OVThread pinned, carrier blocked
Native method (JNI)Pinned
Detect-Djdk.tracePinnedThreads=full
FixReplace 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

PatternDetail
Sync APIUse send() with vthread per request
Async APILess needed with vthreads

9. Migrating from Platform Threads

StepDetail
Identify I/O-bound codeBest candidate
Replace poolsUse newVirtualThreadPerTaskExecutor
Audit synchronizedConvert to ReentrantLock if pinning
Don't migrate CPU-boundKeep platform threads

10. Performance Considerations

PracticeDetail
Don't pool vthreadsCreate per task
Avoid ThreadLocal heavy useAllocates per vthread; use ScopedValues instead
Tune carrier pool-Djdk.virtualThreadScheduler.parallelism=N