Java Concurrency Patterns for High-Throughput Services
With Java 21's Project Loom going GA, the concurrency landscape has fundamentally changed. Here are my benchmark results and patterns for production services handling 10K+ concurrent connections.
Virtual Threads vs Thread Pools
The biggest surprise from our benchmarks: virtual threads consistently outperformed traditional thread pools for I/O-bound workloads by 3-5x. For a typical REST API service:
// Traditional approach
ExecutorService pool = Executors.newFixedThreadPool(200);
// Virtual threads (Java 21+)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> handleRequest(request));
}
Structured Concurrency
Java 21's structured concurrency (preview) makes managing subtasks much cleaner:
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Subtask<User> user = scope.fork(() -> fetchUser(id));
Subtask<Order> orders = scope.fork(() -> fetchOrders(id));
scope.join().throwIfFailed();
return new Response(user.get(), orders.get());
}
Benchmark Results
| Approach | Throughput (req/s) | P99 Latency | Memory |
|---|---|---|---|
| FixedThreadPool(200) | 12,400 | 89ms | 512MB |
| Virtual Threads | 41,200 | 34ms | 380MB |