Modern Java

37. Virtual Threads (Project Loom)

Virtual threads (Java 21, Project Loom) are lightweight JVM-managed threads that dramatically reduce the cost of blocking I/O โ€” you can create millions of them without the memory and scheduling overhead of OS threads.

Java 21+ (standard)

Explanation

Platform (OS) threads are expensive: each requires ~1 MB of stack space and an OS context switch. Virtual threads are cheap JVM-level coroutines โ€” they park on a carrier OS thread when blocked (I/O, sleep, lock) and resume on any available carrier thread when ready. You can create millions of virtual threads without exhausting resources.

Virtual threads are Thread instances โ€” they use the same Thread API, so existing libraries work transparently. Create them with Thread.ofVirtual().start(...) or Executors.newVirtualThreadPerTaskExecutor().

Key limitation: pinning. A virtual thread is pinned to its carrier OS thread when it blocks inside a synchronized block/method, preventing the carrier from running other virtual threads. Avoid synchronized in virtual-thread-heavy code โ€” use java.util.concurrent.locks.ReentrantLock instead. Pinning is detectable via JFR event jdk.VirtualThreadPinned.

Example

import java.util.concurrent.Executors;

public class VirtualThreadsDemo {
    static void blockingTask(int id) throws InterruptedException {
        System.out.printf("Task %d starting on %s%n", id, Thread.currentThread());
        Thread.sleep(100); // parks virtual thread, frees carrier
        System.out.printf("Task %d finished%n", id);
    }

    public static void main(String[] args) throws InterruptedException {
        // Old way: 1000 platform threads would be very expensive
        // New way: 1000 virtual threads โ€” cheap!
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            for (int i = 0; i < 10; i++) {
                final int id = i;
                executor.submit(() -> {
                    try { blockingTask(id); }
                    catch (InterruptedException e) { Thread.currentThread().interrupt(); }
                });
            }
        } // executor.close() waits for all tasks

        // Single virtual thread
        Thread vThread = Thread.ofVirtual()
                .name("my-virtual-thread")
                .start(() -> System.out.println("Running in: " + Thread.currentThread()));
        vThread.join();

        System.out.println("Is virtual: " + vThread.isVirtual()); // true

        // Virtual thread per task โ€” great for high-concurrency servers
        System.out.println("Main thread is virtual: " + Thread.currentThread().isVirtual());
    }
}

Sources