Lock Framework and AQS Architecture
synchronized is a JVM-level built-in lock. While convenient, it lacks flexibility. Starting from JDK 5, the java.util.concurrent.locks package (designed by Doug Lea) introduced a more granular lock system. Its cornerstone is AQS (AbstractQueuedSynchronizer). Mastering AQS is the key to understanding the majority of JUC (Java Util Concurrent) components.
1. ReentrantLock vs. synchronized
| Feature | synchronized |
ReentrantLock |
|---|---|---|
| Implementation | JVM Internal (Bytecode) | Java Class (AQS) |
| Lifecycle | Automatic | Manual (must use finally) |
| Interruptibility | No | lockInterruptibly() |
| Timeout Support | No | tryLock(timeout) |
| Fairness | Non-fair only | Optional Fair/Non-fair |
| Condition Variables | Single wait-set | Multiple Condition objects |
Pro-Tip: The "Interruptible" Advantage
Standard lock.lock() or synchronized will block indefinitely even if a thread is interrupted. lockInterruptibly() allows a thread to "listen" for an interrupt signal while in the waiting queue, enabling it to abandon the wait and handle the exception—critical for breaking deadlocks.
2. AQS: The "Changing Room" Engine
AQS handles the heavy lifting of thread management: queuing, blocking, and waking. It uses a Template Method Pattern, where the parent (AQS) manages the queue, and subclasses define the "locking logic."
2.1 Core Archetype: "State" and "Queue"
Consider AQS as a single-person Changing Room:
- State (The Indicator Light): A
volatile intrepresenting availability.state=0: Green light (Free).state=1: Red light (Occupied).state>1: Reentrancy (Same person entered multiple times).
- CLH Queue (The Waiting Chairs): A doubly-linked list of
Nodeobjects. If the room is occupied, you "grab a chair" and wait.
2.2 Lifecycle of an Acquire (Exclusive)
When you call lock.lock(), AQS executes the acquire() template:
tryAcquire: A quick attempt to turn the door handle (CAS on state).addWaiter: If failed, wrap the thread in aNodeand append it to the tail.shouldParkAfterFailedAcquire: The thread checks its predecessor. If the predecessor is valid, it sets a "SIGNAL" (-1) alarm on that node ("Wake me up when you leave").parkAndCheckInterrupt: The thread enters a deep kernel-mode sleep viaLockSupport.park().
2.3 Lifecycle of a Release (Exclusive)
tryRelease: Decrements state and clears the owner thread.unparkSuccessor: Wakes up the first "sleeper" in the queue.- The Backward Scan: To ensure consistency, AQS scans for the next sleeper from Tail to Head. Because the
prevpointer is set before thetailis updated via CAS, it is the only pointer guaranteed to be atomically consistent during high-concurrency appends.
- The Backward Scan: To ensure consistency, AQS scans for the next sleeper from Tail to Head. Because the
3. Fairness vs. Throughput
- Non-fair Lock (Default): A newly arrived thread can "barge in" and steal the lock if it's currently free, bypassing the queue.
- Why Non-fair? Waking up a thread from kernel sleep (Context Switch) takes ~10,000ns. In that gap, the lock is idle. A non-fair lock allows an "awake" thread to utilize that idle time, significantly increasing System Throughput.
4. Shared Mode: The Domino Effect
While ReentrantLock is exclusive, CountDownLatch and Semaphore use Shared Mode.
- Propagation: When a shared node is awakened, it triggers a
setHeadAndPropagate. It essentially wakes its successor immediately, creating a chain reaction (Domino effect) until all shared waiters are awake.
5. Classic AQS Subclasses
| Class | State Meaning | Mode |
|---|---|---|
| ReentrantLock | Reentrancy Count | Exclusive |
| Semaphore | Available Permits | Shared |
| CountDownLatch | Remaining Tasks | Shared |
| ReadWriteLock | High 16: Read / Low 16: Write | Mixed |
6. StampedLock (JDK 8)
ReentrantReadWriteLock can suffer from "Write Starvation" if there are constant readers. StampedLock solves this with Optimistic Reading:
long stamp = lock.tryOptimisticRead(); // Not a real lock!
// Copy data...
if (!lock.validate(stamp)) { // Check if a writer intervened
stamp = lock.readLock(); // Fallback to pessimistic lock
// Re-copy data...
}
Optimistic reading has zero interlocking cost until a writer actually modifies the data.
Summary Decision Matrix
- Simple Exclusion:
synchronized(JVM optimized). - Advanced Control:
ReentrantLock(interrupts, timeouts, multi-conditions). - Read-Heavy:
ReentrantReadWriteLock. - Ultra-Read (Cache-like):
StampedLock(Optimistic). - Synchronization Barriers:
CountDownLatch/CyclicBarrier.