---
title: "Concurrency and virtual threads"
chapter: "12"
---

# Concurrency and virtual threads

Concurrency allows tasks to overlap. Correctness comes before throughput.

## Threads and tasks

Prefer submitting tasks to an `ExecutorService` over manually creating platform
threads. Shut executors down and handle interruption. `Future` represents a
result available later; `CompletableFuture` composes asynchronous stages.

## Shared-state hazards

A race occurs when outcome depends on timing. `volatile` gives visibility and
ordering for a variable but does not make compound actions such as `count++`
atomic. Use locks, atomic types, concurrent collections, confinement, or
immutable messages.

Every lock design needs ownership, ordering, timeout/interruption policy, and
minimal critical sections. Lock-order cycles can deadlock.

## Virtual threads

Virtual threads are lightweight Java threads finalized in Java 21. They are
excellent for large numbers of mostly blocking I/O tasks while preserving a
simple thread-per-task style.

They do not make CPU work faster and should not be pooled as scarce resources.
Bound the actual scarce resource—database connections, remote concurrency, or
memory—with semaphores/pools. Avoid long pinning around native calls or
`synchronized` regions that block scalability; use JFR to observe pinning.

```java
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
  var futures = requests.stream().map(r -> executor.submit(() -> call(r))).toList();
}
```

Structured concurrency remains a preview API in Java 25. Preview features
require explicit compilation/runtime flags and may change.

## Feynman check

Virtual threads are lightweight customer tickets, not extra cashiers. They let
many customers wait cheaply, but the database counter still serves only a
limited number at once.
