How ReentrantLock Works: Inside Java’s AbstractQueuedSynchronizer
This article dissects Java’s ReentrantLock implementation, detailing its delegation to AbstractQueuedSynchronizer, the lock acquisition process, the role of non‑fair and fair sync subclasses, the internal CLH queue mechanics, and how unlocking is handled, while comparing Lock with synchronized.
When discussing concurrency, ReentrantLock inevitably brings up AbstractQueuedSynchronizer (AQS), the abstract queue‑based synchronizer framework that underlies many Java synchronization utilities such as Semaphore and CountDownLatch. This article uses ReentrantLock as the entry point to explore AQS.
1. ReentrantLock call process
ReentrantLock delegates all Lock interface operations to an inner Sync class that extends AbstractQueuedSynchronizer:
static abstract class Sync extends AbstractQueuedSynchronizer Synchas two subclasses to support fair and non‑fair locking, with non‑fair being the default:
final static class NonfairSync extends Sync
final static class FairSync extends SyncThe default non‑fair lock acquisition proceeds as follows (illustrated by the diagram below):
2. Lock implementation (acquire)
AbstractQueuedSynchronizer builds a CLH queue of waiting threads; when a thread finishes (lock.unlock()), it wakes its successor, while the running thread is not in the queue.
Thread blocking is performed via LockSupport.park(), which eventually calls the native pthread_mutex_lock to block the thread in the kernel.
The CLH queue is a virtual queue; nodes are linked only by predecessor/successor references. Non‑fair locks allow a newly arriving thread to acquire the lock before queued threads, improving throughput.
2.1 Sync.nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}The method first checks the lock state. If the state is zero, it attempts a CAS to set it to the acquire count (normally 1). Successful CAS means the current thread becomes the running thread and does not enter the wait queue. If the state is non‑zero but owned by the current thread, the count is simply incremented without CAS, implementing a re‑entrant (biased) lock.
2.2 AbstractQueuedSynchronizer.addWaiter
addWaiterwraps a thread that cannot acquire the lock into a Node and appends it to the queue tail:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}If the tail is null or CAS fails, enq repeatedly attempts to set the tail using CAS until it succeeds.
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
Node h = new Node(); // dummy header
h.next = node;
node.prev = h;
if (compareAndSetHead(h)) {
tail = node;
return h;
}
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}The queue nodes also carry a waitStatus field with values such as SIGNAL(-1), CANCELLED(1), CONDITION(-2), PROPAGATE(-3) and 0 (no status).
2.3 AbstractQueuedSynchronizer.acquireQueued
acquireQueuedblocks the node that has been added to the queue, but first re‑tries acquisition via tryAcquire. If it still cannot acquire, it may park the thread based on the predecessor’s status.
final boolean acquireQueued(final Node node, int arg) {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
}The helper shouldParkAfterFailedAcquire examines the predecessor’s waitStatus to decide whether the current thread should block, skip cancelled nodes, or set the predecessor’s status to SIGNAL.
3. Unlocking
When a thread releases the lock, AbstractQueuedSynchronizer.release invokes tryRelease (implemented in the concrete Sync subclass). If the lock becomes free, it calls unparkSuccessor to wake the first waiting thread.
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
} private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}The method searches forward from the head; if the immediate successor is cancelled or null, it walks backwards from the tail to find a valid thread to unpark.
4. Lock vs. synchronized
Both Lock and synchronized rely on a queue of waiting threads, but Lock uses the AQS‑based CLH queue with explicit CAS operations, while synchronized’s JVM implementation employs a more refined queue (ContentionList and EntryList) and includes spin‑lock optimizations. Lock is more extensible, allowing custom synchronizers (e.g., read‑write locks) and richer condition support.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect's Must-Have
Professional architects sharing high‑quality architecture insights. Covers high‑availability, high‑performance, high‑stability designs, big data, machine learning, Java, system, distributed and AI architectures, plus internet‑driven architectural adjustments and large‑scale practice. Open to idea‑driven, sharing architects for exchange and learning.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
