Java Concurrency Landscape: A Topological Overview
Why do we need concurrency? In short: CPUs are too fast, and I/O is too slow. Single-threaded execution cannot fully utilize hardware potential because threads spend 95% of their time waiting for network or disk logic. Concurrency allows the CPU to switch to other tasks while waiting for I/O.
However, concurrency is not a free lunch. it introduces three fundamental challenges: Visibility, Atomicity, and Ordering. The entire Java concurrency ecosystem—from synchronized to java.util.concurrent (JUC)—is built to solve these three problems.
1. Process, Thread, and Coroutine
1.1 Process (Resource Unit)
A process is the basic unit of OS resource allocation. Processes have isolated memory spaces (Heap, Stack, Code). Communication requires Inter-Process Communication (IPC) like Sockets or Shared Memory.
1.2 Thread (Scheduling Unit)
A thread is the basic unit of CPU scheduling. Threads within a process share the Heap and Metaspace but have private Stacks and Program Counters (PC).
- Java Thread Model: In HotSpot JVM, Java threads are mapped 1:1 to OS threads. A
Thread.start()calls the kernel (viapthread_create) to allocate a 1MB stack. This makes thread creation and switching expensive.
1.3 Virtual Threads (JDK 21+)
Virtual threads (Project Loom) are "User-mode" threads managed by the JVM rather than the OS. They are extremely lightweight (~1KB stack) and allow for millions of concurrent tasks on a single process. They are ideal for I/O-heavy workloads but offer no advantage for CPU-bound computation.
| Feature | Process | Platform Thread | Virtual Thread |
|---|---|---|---|
| Scheduler | OS | OS | JVM |
| Creation Cost | High (MBs) | Medium (~1MB) | Ultra-light (~1KB) |
| Switching Cost | High (Page Tables) | Medium (Kernel-mode) | Low (User-mode) |
| Capacity | Hundreds | Thousands | Millions |
2. The Three Pillars of Concurrency Bugs
2.1 Visibility
One thread's update is not immediately visible to another. Hardware root: CPUs use L1/L2 caches. Thread A modifies a value in its core's cache; Thread B reads the stale value from its own cache before the hardware flushes the modification to Main Memory.
2.2 Atomicity
An operation is interrupted midway by the scheduler.
The classic count++ is actually three instructions: Read, Increment, Write. If two threads execute this simultaneously, one's update may overwrite the other, leading to data loss.
2.3 Ordering
Execution order differs from the code order.
Compilers and CPUs reorder instructions to maximize throughput (as-if-serial). In multi-threading, this leads to disasters like the Double-Checked Locking (DCL) singleton exposing a non-null but uninitialized object.
3. The JUC Topography
The java.util.concurrent (JUC) package, designed by Doug Lea, provides high-level abstractions for common concurrency patterns.
- Locks & Synchronization:
synchronized,ReentrantLock,ReadWriteLock,AQS. - Atomic Operations:
AtomicInteger,LongAdder(High-concurrency counters). - Concurrent Collections:
ConcurrentHashMap,CopyOnWriteArrayList,BlockingQueue. - Thread Pools:
ThreadPoolExecutor,ForkJoinPool. - Coordinators:
CountDownLatch,Semaphore,CyclicBarrier. - Async Pipelines:
CompletableFuture.
4. Designing for Thread-Safety: A Hierarchy
Designing for concurrency should follow a path of least resistance:
- Immutability: The objects never change. They are naturally safe (e.g.,
String). - Thread Confinement: Keep data within a single thread (e.g.,
ThreadLocal). - Non-blocking/CAS: Use
Atomicclasses to avoid locking overhead. - Locking: Use
synchronizedorLockonly when atomic compound operations are required.