ReentrantLock is a reentrant mutual exclusion mechanism built atop the AbstractQueuedSynchronizer (AQS) framework. It provides flexible thread synchronization in Java, supporting both fair and non-fair acquisition policies.
Structural Overview
ReentrantLock implements the Lock interface. Internally it defines a nested Sync class extending AQS, which is further specialized into UnfairSync and FairSync for non-fair and fair strategies respectively. This separation enables policy-based lock behavior while sharing core mechanics.
Comparison With Synchronized
synchronized is JVM-managed, operates on methods or blocks, and always uses a fair queuing approach. ReentrantLock offers:
- Choice between fair and non-fair modes.
- Explicit lock control via
tryLock(timeout),lockInterruptibly(). - Manual lock acquisition and release, requiring
unlock()in afinallyblock.
While more powerful, ReentrantLock demands careful handling to avoid leaks or deadlocks.
Lock Acquisition Mechanics
Core Sync Abstractions
Sync declares an abstract lock() method and supplies reusalbe routines:
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
final boolean attemptNonFairAcquire(int units) {
Thread cur = Thread.currentThread();
int state = getState();
if (state == 0) {
if (compareAndSetState(0, units)) {
setExclusiveOwnerThread(cur);
return true;
}
} else if (cur == getExclusiveOwnerThread()) {
int next = state + units;
if (next < 0)
throw new Error("Lock count overflow");
setState(next);
return true;
}
return false;
}
protected final boolean attemptRelease(int units) {
int remaining = getState() - units;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean fullyReleased = false;
if (remaining == 0) {
fullyReleased = true;
setExclusiveOwnerThread(null);
}
setState(remaining);
return fullyReleased;
}
}
UnfairSync Strategy
UnfairSync tries immediate CAS on the state before falling back to AQS queue processing:
static final class UnfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int units) {
return attemptNonFairAcquire(units);
}
}
The attemptNonFairAcquire allows a thread to bypass the queue and grab the lock if available, ignoring waiting threads' order.
FairSync Strategy
FairSync strictly respects the FIFO order of the wait queue:
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int units) {
Thread cur = Thread.currentThread();
int state = getState();
if (state == 0) {
if (!hasQueuedPredecessors() && compareAndSetState(0, units)) {
setExclusiveOwnerThread(cur);
return true;
}
} else if (cur == getExclusiveOwnerThread()) {
int next = state + units;
if (next < 0)
throw new Error("Lock count overflow");
setState(next);
return true;
}
return false;
}
}
Here, hasQueuedPredecessors ensures no earlier queued thread is waiting before attempting CAS.
AQS Acquisition Pipeline
acquire(arg): TriestryAcquire; on failure, enqueues the thread and may park it.
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
addWaiter(mode): Wraps the thread into a node and appends to tail, retrying withenqon contention.
private Node addWaiter(Node mode) {
Node n = new Node(Thread.currentThread(), mode);
Node prev = tail;
if (prev != null) {
n.setPrev(prev);
if (compareAndSetTail(prev, n)) {
prev.setNext(n);
return n;
}
}
enq(n);
return n;
}
acquireQueued(node, arg): Loops, checking if the node is next after head to retry acquisition; otherwise parks.
final boolean acquireQueued(final Node n, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
Node pred = n.getPrev();
if (pred == head && tryAcquire(arg)) {
setHead(n);
pred.setNext(null);
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(pred, n) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed) cancelAcquire(n);
}
}
enq(node): Ensures node insertion evenif tail was null initially.
private Node enq(final Node n) {
for (;;) {
Node t = tail;
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
n.setPrev(t);
if (compareAndSetTail(t, n)) {
t.setNext(n);
return t;
}
}
}
}
shouldParkAfterFailedAcquire(pred, node): Adjusts predecessor's wait status to signal and decides if parking is safe.
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int st = pred.getWaitStatus();
if (st == Node.SIGNAL) return true;
if (st > 0) {
do {
node.setPrev(pred = pred.getPrev());
} while (pred.getWaitStatus() > 0);
pred.setNext(node);
} else {
compareAndSetWaitStatus(pred, st, Node.SIGNAL);
}
return false;
}
Lock Release Mechanics
Release Entrypoint
public final boolean release(int arg) {
if (attemptRelease(arg)) {
Node h = head;
if (h != null && h.getWaitStatus() != 0)
unparkSuccessor(h);
return true;
}
return false;
}
Successor Wake-Up
private void unparkSuccessor(Node node) {
int st = node.getWaitStatus();
if (st < 0) compareAndSetWaitStatus(node, st, 0);
Node succ = node.getNext();
if (succ == null || succ.getWaitStatus() > 0) {
succ = null;
for (Node t = tail; t != null && t != node; t = t.getPrev())
if (t.getWaitStatus() <= 0) succ = t;
}
if (succ != null) LockSupport.unpark(succ.getThread());
}
Traversing from tail to head during wake-up avoids missing nodes due to the two-step linking process in addWaiter, ensuring all eligible waiters can be resumed correctly.