Virtual Threads
The biggest change to Java concurrency since Java 5. Virtual threads are lightweight threads managed by the JVM, not the OS. Create millions of threads with minimal memory overhead.
Creating Virtual Threads
// Before Java 21 - Platform Threads (expensive!)
ExecutorService executor = Executors.newFixedThreadPool(100);
for (int i = 0; i < 10000; i++) {
executor.submit(() -> handleRequest()); // Limited to ~100 concurrent
}
// Java 21+ - Virtual Threads (millions possible!)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 1_000_000; i++) {
executor.submit(() -> handleRequest()); // Each gets its own virtual thread!
}
}Interactive Thread Simulation
Platform Threads
Virtual Threads
Structured Concurrency
Treat groups of concurrent tasks as a single unit of work. When one fails, all are cancelled. No more lost exceptions, resource leaks, or orphan threads.
Traditional (ExecutorService)
Future<User> user = exec.submit(
() -> fetchUser(id));
Future<Orders> orders = exec.submit(
() -> fetchOrders(id));
Future<Recs> recs = exec.submit(
() -> fetchRecs(id)); // FAILS!
// Problems:
// • Other tasks keep running
// • Exception may be lost
// • Manual cleanup neededRun to see...
Structured Concurrency
try (var scope = StructuredTaskScope
.ShutdownOnFailure()) {
scope.fork(() -> fetchUser(id));
scope.fork(() -> fetchOrders(id));
scope.fork(() -> fetchRecs(id));
scope.join();
scope.throwIfFailed();
}
// Benefits:
// • Auto-cancel on failure
// • Clean exception handling
// • Resources auto-cleanedRun to see...
Structured Concurrency Timeline
Scoped Values
Share immutable data across threads safely. The modern replacement for ThreadLocal that works perfectly with virtual threads - no memory leaks, automatic inheritance, clear scope boundaries.
ThreadLocal (Legacy)
ThreadLocal<String> USER =
new ThreadLocal<>();
USER.set("alice@example.com");
try {
doWork();
// Child threads: USER.get() = null!
} finally {
USER.remove(); // Easy to forget!
}Run to see...
ScopedValue (Java 21+)
ScopedValue<String> USER =
ScopedValue.newInstance();
ScopedValue.where(USER, "alice")
.run(() -> {
doWork();
// Child threads inherit USER!
});
// Auto-unbound. No cleanup needed!Run to see...
Scoped Values Timeline
Virtual Thread Pinning Fix
In Java 21-22, virtual threads got "pinned" to carrier threads when entering synchronized blocks, preventing other virtual threads from using that carrier. Java 23-24 fixes this critical limitation.
Java 21-22 (Pinning Issue)
// Java 21-22: synchronized causes pinning
synchronized (lock) {
// Virtual thread PINNED to carrier!
// Carrier cannot run other VTs
blockingIO(); // Carrier blocked too
}
// Other virtual threads starveRun to see...
Java 23-24 (Pinning Fixed)
// Java 23-24: synchronized no longer pins
synchronized (lock) {
// Virtual thread NOT pinned!
// Can unmount during I/O
blockingIO(); // Carrier freed
}
// Other virtual threads can runRun to see...
What is Pinning?
Pinning occurs when a virtual thread cannot unmount from its carrier thread:
- •Java 21-22: synchronized blocks pin the virtual thread
- •Native methods (JNI) still cause pinning
- •Java 23-24: synchronized no longer pins!
// Run with JFR to detect pinning events
java -XX:StartFlightRecording=filename=rec.jfr \
-Djdk.tracePinnedThreads=full \
MyApp.java
// Look for jdk.VirtualThreadPinned eventsObservability & Monitoring
As Virtual Threads became the standard, Java 24 added better tools to monitor and debug them. New jcmd commands and MXBeans provide visibility into the virtual thread scheduler.
New jcmd Commands
$ jcmd <pid> Thread.vthread_scheduler ForkJoinPool-1-worker-1: WAITING ForkJoinPool-1-worker-2: RUNNING vthread[#21] ForkJoinPool-1-worker-3: PARKED ... Virtual threads: 1,234,567 active
$ jcmd <pid> Thread.vthread_pollers Read poller: 42 threads waiting Write poller: 18 threads waiting Connect poller: 5 threads waiting
VirtualThreadSchedulerMXBean
// Programmatic monitoring
MBeanServer mbs = ManagementFactory
.getPlatformMBeanServer();
ObjectName name = new ObjectName(
"java.lang:type=VirtualThreadScheduler");
// Get scheduler metrics
long mounted = (Long) mbs.getAttribute(
name, "MountedVirtualThreadCount");
long queued = (Long) mbs.getAttribute(
name, "QueuedVirtualThreadCount");
int parallelism = (Integer) mbs.getAttribute(
name, "Parallelism");
System.out.printf(
"Mounted: %d, Queued: %d, Workers: %d%n",
mounted, queued, parallelism);Available Metrics
Putting It All Together
Virtual Threads + Structured Concurrency + Scoped Values = The future of Java concurrency. Here's how they work together:
// The Future of Java Concurrency (Java 24+)
// 1. Define scoped values for request context
static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();
static final ScopedValue<RequestId> REQUEST_ID = ScopedValue.newInstance();
void handleRequest(Request req) {
User user = authenticate(req);
RequestId id = RequestId.generate();
// 2. Bind scoped values for this request
ScopedValue.where(CURRENT_USER, user)
.where(REQUEST_ID, id)
.run(() -> {
// 3. Use structured concurrency for parallel work
try (var scope = StructuredTaskScope.ShutdownOnFailure()) {
// 4. All forked tasks are virtual threads
// They automatically inherit scoped values!
Subtask<Profile> profile = scope.fork(() ->
fetchProfile(CURRENT_USER.get())); // Has access!
Subtask<List<Order>> orders = scope.fork(() ->
fetchOrders(CURRENT_USER.get())); // Has access!
Subtask<List<Recommendation>> recs = scope.fork(() ->
fetchRecommendations()); // Has access!
scope.join();
scope.throwIfFailed();
// 5. All succeeded, return response
return new Response(profile.get(), orders.get(), recs.get());
}
// Scope closes: all threads cleaned up, values unbound
});
}
// Benefits:
// ✓ Millions of concurrent requests (Virtual Threads)
// ✓ Clean error handling (Structured Concurrency)
// ✓ Safe context propagation (Scoped Values)
// ✓ No memory leaks, no orphan threads, no lost exceptionsConcurrency Evolution: Java 21 → 24
Virtual Threads
Millions of lightweight threads
Structured Concurrency
Tasks as a unit of work
Scoped Values
Safe context propagation
Pinning Fixed
synchronized no longer pins
Observability
Monitor virtual threads
Demo Showcase
Auto-playing demonstration of all Java concurrency features
Virtual Threads
Java 21 - Platform vs Virtual Thread Comparison