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.
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.