11. Executors & Virtual Threads

Asynchronous Concurrency, CompletableFuture & Virtual Threads (Project Loom)

Master ExecutorService thread pools, CompletableFuture reactive pipelines, and Java 21 Virtual Threads (Project Loom) mounting/unmounting mechanics.

๐Ÿš€ The Executor Framework & Thread Pools

Managing threads manually (new Thread().start()) is expensive and dangerous. The Executor Framework decouples task submission from thread execution through managed Thread Pools:

  • Analogy: A Freight Dispatch Center & Micro-Drone Delivery Fleet.
    • Platform Threads (Thread): Heavy cargo delivery trucks. Assigning 1 truck to deliver 1 envelope consumes massive fuel and road space (~1 MB RAM stack allocation per thread).
    • Thread Pool (ExecutorService): A fixed fleet of 10 trucks stationed at a central depot, continuously reusing trucks to haul incoming delivery packages from a central warehouse queue.
    • Virtual Threads (Java 21 Project Loom): A fleet of 1,000,000 lightweight micro-drones. When a drone reaches a closed customer door (blocking I/O operation), it unmounts its package, returns to the mother ship (Carrier Thread), and lets other drones fly!
                   Platform vs. Virtual Threads Architecture
                                       โ”‚
        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
        โ–ผ                                                             โ–ผ
Platform Threads (1:1 OS Kernel Mapping)                    Virtual Threads (M:N Carrier Thread Multiplexing)
Java Thread 1 โ”€โ”€โ–บ OS Thread 1 (~1 MB Stack)                 Virtual Thread 1 โ”
Java Thread 2 โ”€โ”€โ–บ OS Thread 2 (~1 MB Stack)                 Virtual Thread 2 โ”œโ”€โ–บ Carrier OS Thread (~1 KB Stack)
Max capacity: ~2,000 threads before OOM                     Virtual Thread N โ”˜
                                                            Max capacity: 1,000,000+ threads!

โšก Asynchronous Pipelines with CompletableFuture

CompletableFuture allows building non-blocking asynchronous data pipelines with functional callbacks:

import java.util.concurrent.CompletableFuture;

public class AsyncPipelineDemo {
    public static void main(String[] args) {
        // Asynchronous non-blocking pipeline
        CompletableFuture.supplyAsync(() -> {
            System.out.println("Fetching user profile from remote API...");
            return "User_101";
        }).thenApply(userId -> {
            System.out.println("Enriching user details for: " + userId);
            return userId + " [VIP Status]";
        }).thenAccept(result -> {
            System.out.println("Final Result Delivered: " + result);
        }).exceptionally(ex -> {
            System.err.println("Pipeline Failed: " + ex.getMessage());
            return null;
        });

        // Keep main thread alive for async completion
        try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
    }
}

๐Ÿงต Java 21 Virtual Threads (Project Loom)

Java 21 introduced Virtual Threads, lightweight threads managed directly by the JVM runtime rather than the underlying host OS:

Mounting & Unmounting Mechanics

When a Virtual Thread executes a blocking I/O operation (Thread.sleep(), Socket read, Database JDBC query):

  1. The JVM unmounts the Virtual Thread from its underlying Carrier Thread (a standard OS thread).
  2. The Carrier Thread is freed immediately to execute other Virtual Threads.
  3. Once the blocking I/O operation completes, the JVM remounts the Virtual Thread onto any available Carrier Thread to resume execution!
// Instantiating 100,000 Virtual Threads in Java 21!
public class VirtualThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        // Uses Virtual Thread Executor
        try (var executor = java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor()) {
            for (int i = 0; i < 100_000; i++) {
                final int taskId = i;
                executor.submit(() -> {
                    // Simulates blocking network I/O
                    Thread.sleep(1000);
                    return taskId;
                });
            }
        } // Auto-closes and awaits all 100,000 virtual threads in ~1 second!

        System.out.println("100,000 Virtual Threads Executed Successfully!");
    }
}

[!CAUTION] Virtual Thread Pinning Hazard: Executing a blocking operation inside a synchronized block or native method "pins" the Virtual Thread to its Carrier Thread, preventing unmounting.

Best Practice: Replace synchronized blocks with ReentrantLock in Virtual Thread codebases to avoid carrier thread pinning!


โ“ Conceptual Quizzes

Knowledge Check

What is the key execution difference between Platform Threads and Virtual Threads in Java 21?

Knowledge Check

What causes Virtual Thread Pinning in Java 21, and how do you resolve it?


๐Ÿ’ป Practice Problems

Problem: High-Concurrency Virtual Thread HTTP Fetcher

Write a Java 21 program that spawns 1,000 Virtual Threads using Executors.newVirtualThreadPerTaskExecutor(), where each thread simulates a 500ms API call, and verifies execution time is ~500ms rather than 500 seconds.

On this page