How Does Java’s AQS Implement Exclusive and Shared Locks? A Deep Dive with Code
This article explains the inner workings of Java's AbstractQueuedSynchronizer (AQS), covering exclusive and shared lock acquisition, interrupt handling, timeout control, and release mechanisms, complete with detailed code examples and flow‑chart illustrations.
Introduction
AQS (AbstractQueuedSynchronizer) is the foundation for building Java synchronization components. It follows the Template Method pattern, allowing subclasses to implement abstract methods that manage synchronization state while AQS provides most of the heavy‑lifting logic.
Exclusive Mode
1. Exclusive acquisition
The acquire(int arg) method attempts to obtain exclusive ownership. It is not interrupt‑responsive; if acquisition fails, the thread is enqueued in a CLH FIFO queue and spins until it becomes the head.
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}The helper methods are defined as follows: tryAcquire: Subclass implements the actual lock acquisition logic. addWaiter: Enqueues the current thread at the tail of the CLH queue when acquisition fails. acquireQueued: Spins while the thread is not the head, attempting to acquire the lock. selfInterrupt: Sets the thread’s interrupt status.
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed) cancelAcquire(node);
}
}2. Interrupt‑responsive acquisition
acquireInterruptibly(int arg)first checks the thread’s interrupt status and throws InterruptedException if already interrupted. If the lock cannot be obtained immediately, it delegates to doAcquireInterruptibly, which behaves like acquireQueued but throws the exception as soon as an interrupt is detected.
public final void acquireInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
private void doAcquireInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed) cancelAcquire(node);
}
}3. Timed acquisition
The method tryAcquireNanos(int arg, long nanosTimeout) adds timeout control to the interrupt‑responsive version. It records a deadline, attempts immediate acquisition, and if that fails, enters a loop that parks the thread for the remaining nanoseconds, handling timeout and interrupt conditions.
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
if (nanosTimeout <= 0L) return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
// timeout handling
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L) return false;
if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed) cancelAcquire(node);
}
}4. Exclusive release
After the protected operation finishes, release(int arg) invokes the subclass‑implemented tryRelease. If successful, it wakes up the next 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;
}Shared Mode
1. Shared acquisition
Shared acquisition allows multiple threads to hold the lock simultaneously (e.g., read locks). The method acquireShared(int arg) first tries tryAcquireShared; if it returns a negative value, the thread enters doAcquireShared and spins similarly to the exclusive case.
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted) selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed) cancelAcquire(node);
}
}The return value of tryAcquireShared is an int; a non‑negative result indicates successful acquisition.
2. Shared release
Releasing a shared lock uses releaseShared(int arg), which calls tryReleaseShared and, on success, wakes up waiting successors.
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}FIFO Synchronization Queue
AQS maintains a FIFO CLH queue. When a thread fails to acquire the lock, it is appended to the queue and spins, repeatedly checking whether its predecessor is the head. Once the head releases the lock, it unparks its successor, which then attempts acquisition. This mechanism ensures fairness and orderly wake‑ups.
In AQS, a FIFO synchronization queue holds threads that failed to acquire the lock; each thread spins while waiting for its predecessor to become the head, acquires the lock when possible, and upon releasing the lock, wakes up its successor.
Illustrations
Acquisition flow chart (exclusive mode):
Overall AQS process diagram:
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.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
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.
