Java concurrency guide: threads, ExecutorService, CompletableFuture, virtual threads, thread safety, and common pitfalls.
Concurrency: Threads, Executors, and Virtual Threads
Java's concurrency model has evolved from raw threads (Java 1.0) through the
ExecutorService framework (Java 5) to Virtual Threads (Java 21). This page covers
the practical patterns you need for everyday concurrent programming.
Thread basics
Never call
thread.run()- that executes on the current thread. Always usethread.start().
ExecutorService
Instead of managing threads manually, use an ExecutorService to manage a thread pool:
Thread pool types
CompletableFuture
CompletableFuture is Java's equivalent of JavaScript's Promise - it supports
chaining, combining, and error handling:
Basic chaining
Error handling
Combining futures
Running on a specific executor
Timeout (Java 9+)
Virtual Threads (Java 21)
Virtual threads are lightweight threads managed by the JVM, not the OS. You can create millions of them without exhausting memory or OS thread limits.
Creating virtual threads
Virtual vs platform threads
When to use virtual threads
Avoiding pinning
Virtual threads are pinned (cannot be parked) inside synchronized blocks when
they perform blocking operations. Use ReentrantLock instead:
Thread safety
Immutability (best approach)
Atomic variables
Synchronized blocks
ReentrantLock
More flexible than synchronized (try-lock, timed lock, interruptible):