Part 3 · 2 chapters · ~12 min
Concurrency and Virtual Threads
The Java memory model and synchronized, java.util.concurrent (executors, locks, atomics, concurrent collections), CompletableFuture, virtual threads measured, structured concurrency and scoped values, reactive programming with Reactor and when it is still worth it, and connection pools as the real limit.
5
From thread pools to virtual threads
code
// measured on this machine (Java 26):
try (var ex = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 100_000; i++) ex.submit(() -> { Thread.sleep(1000); return 0; });
} // 1,490 ms
try (var ex = Executors.newFixedThreadPool(200)) {
for (int i = 0; i < 2_000; i++) ex.submit(() -> { Thread.sleep(1000); return 0; });
} // 10,049 msVIRTUAL THREADS, MEASURED
Java 26 on this machine: tasks that each block for 1 second
swipe the figure sideways, or tap expand for full screen
1/4
platform threads
A classic thread pool with 200 platform (OS) threads runs 200 blocking tasks at a time: 2,000 tasks sleeping one second need ten waves, 10,049 ms measured.
200 at a time: ten wavesthread-per-request caps concurrency
6
java.util.concurrent and structured concurrency
code
// CompletableFuture: compose async steps
CompletableFuture<Quote> quote = CompletableFuture.supplyAsync(() -> rates.quote(pair), executor)
.orTimeout(500, TimeUnit.MILLISECONDS)
.exceptionally(e -> Quote.stale(pair));
// structured concurrency (preview in recent JDKs): subtasks cannot outlive the scope
try (var scope = StructuredTaskScope.open()) {
var acct = scope.fork(() -> accounts.find(id));
var limits = scope.fork(() -> limitsService.get(id));
scope.join(); // first failure cancels the other
return new Dashboard(acct.get(), limits.get());
}
// concurrent building blocks
ConcurrentHashMap<String, LongAdder> counts = new ConcurrentHashMap<>();
counts.computeIfAbsent(route, k -> new LongAdder()).increment();