Encodera

Thanks for reading!

Donate to the site — we work hard for you.

How to use virtual threads

Short answer

Virtual threads (Java 21) are very lightweight: you can create thousands of them. The easiest way is an executor that runs every task in a new virtual thread:

void main() {
    var done = new AtomicInteger();
    long start = System.nanoTime();
 
    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        for (int i = 0; i < 10_000; i++) {
            executor.submit(() -> {
                Thread.sleep(Duration.ofMillis(500));
                return done.incrementAndGet();
            });
        }
    }
 
    long ms = (System.nanoTime() - start) / 1_000_000;
    IO.println(done.get() + " tasks, less than 3 s: " + (ms < 3000));
}

Output:

10000 tasks, less than 3 s: true

10,000 tasks that each wait half a second finish in about half a second. With a regular pool of 10 threads it would take more than eight minutes.

A single thread

Thread.ofVirtual().start() starts one virtual thread, and join() waits for it to finish:

void main() throws InterruptedException {
    Thread thread = Thread.ofVirtual().start(() -> IO.println("Hello from a virtual thread"));
    thread.join();
 
    IO.println(thread.isVirtual());
}

Output:

Hello from a virtual thread
true

When they fit

Virtual threads are good for tasks that mostly wait: network calls, database queries, files. While a thread waits, the JVM gives the CPU to others. For heavy computations they do not help — they do not add CPU cores.

Common mistake

Putting virtual threads into a fixed-size pool. There is no need to save them: create a new thread for every task. If you need to limit load, for example the number of concurrent database queries, use a Semaphore.

Functions used in this recipe

Thanks for reading!

Donate to the site — we work hard for you.