Threads, ExecutorService, CompletableFuture, and virtual threads—concurrent Java in practice.
Concurrency is central to scalable Java apps. Here’s a practical overview of threads, executors, and virtual threads.
Java concurrency and threads
Core concepts
Threads — Units of execution. Creating many platform threads is expensive; use a pool. ExecutorService (e.g. Executors.newFixedThreadPool) manages threads and tasks.
CompletableFuture — Async composition: chain thenApply, thenCompose, allOf. Non-blocking; combine I/O and CPU work. Prefer over raw Future when you need composition.
Synchronization — synchronized and locks for shared mutable state. Prefer concurrent collections (ConcurrentHashMap, BlockingQueue) and immutable data when possible.
Virtual threads (Java 21) — Lightweight threads; millions possible. Use for blocking I/O (e.g. DB, HTTP). Executors.newVirtualThreadPerTaskExecutor() or Thread.startVirtualThread(Runnable).
Concurrency approach (Java backend survey):
Primary concurrency approach
Best practices
Don’t block virtual threads with synchronized (use ReentrantLock if needed). Prefer virtual threads for request-per-thread and I/O-bound work. Use CompletableFuture or reactive for composition when it fits your stack.
Virtual threads explained:
Takeaway
Use ExecutorService for thread pools; use CompletableFuture for async composition. On Java 21, adopt virtual threads for I/O-bound services to scale without the cost of platform threads.