0%
Virtual Threads: Concurrency Without a Pool

Virtual threads

The cost of the thread-per-request model, structured concurrency, and what still blocks.

10-15 min

Virtual Threads: Concurrency Without a Pool

A classic Java server handles each request on a thread taken from a pool. That thread is an operating system thread: it costs about a megabyte of reserved stack, creating it takes hundreds of microseconds, and the kernel has to schedule it. So you keep few of them, two hundred or four hundred, and recycle them. But what does that thread do during the request? It waits. It waits for PostgreSQL for eight milliseconds, then for a third-party HTTP service for a hundred, then for Redis. In a 150-millisecond request, the CPU may work for three milliseconds. The rest of the time, a scarce and expensive resource sits idle, doing nothing.

When the load exceeds the pool size, requests pile up in a queue. Response times collapse even though the processors are at ten percent utilization. The usual answer to this wall is to switch to reactive programming: no more blocking, just callbacks, CompletableFuture, or reactive streams. It works, and it's expensive: the code gets turned inside out, call stacks become unusable, the debugger can no longer follow the flow, and the entire chain must become non-blocking or you lose all the benefit. Virtual threads, finalized in Java 21, offer a different deal: keep the code blocking, sequential, and readable, by making the thread itself nearly free.

Comments

Comments are powered by GitHub Discussions

Sign in with GitHub to join the discussion

Link copied!