Deep Dive into Java AQS Shared‑Lock Implementation
This article explains the execution flow, source‑code details, and wake‑up logic of Java's AbstractQueuedSynchronizer shared‑lock mode, covering acquireShared, doAcquireShared, setHeadAndPropagate, doReleaseShared, and releaseShared methods with full code snippets and practical guidance.
Execution Overview
When a thread calls acquireShared() it attempts to obtain the shared resource; on success it enters the critical section, otherwise it creates a shared node, enqueues it in a FIFO queue and parks until it is unparked.
Releasing a shared lock is performed by releaseShared(), which wakes up waiting nodes if the release succeeds.
Source‑Code Deep Analysis
The top‑level entry for acquiring a shared lock is acquireShared(int arg). It tries to acquire the resource via tryAcquireShared(arg). If the return value is negative, the thread calls doAcquireShared(arg) to join the wait queue.
acquireShared(int arg)
public final void acquireShared(int arg) {
// try to acquire shared lock; return < 0 means failure
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
} tryAcquireShared()is meant to be implemented by the subclass; its return value determines the next steps:
Negative – acquisition failed, enqueue and park.
Zero – acquisition succeeded but no remaining permits; successors are not woken.
Positive – acquisition succeeded and there are remaining permits; successors should be woken.
doAcquireShared(int arg)
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null;
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}The method adds a shared node to the queue, then repeatedly checks whether its predecessor is the head. If so, it retries acquisition; on success it calls setHeadAndPropagate to make the node the new head and possibly wake successors.
setHeadAndPropagate(Node node, int propagate)
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // current head
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}Besides setting the new head, this method decides whether to propagate the wake‑up signal. If propagate is positive or the previous head indicated a pending signal, it attempts to unpark the next shared successor.
doReleaseShared()
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
unparkSuccessor(h);
} else if (ws == 0 &&
compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) {
// continue loop
}
if (h == head)
break;
}
}
}The method walks from the head, waking up successors whose waitStatus is SIGNAL. If the status is 0, it tries to set it to PROPAGATE so that later releases can continue the propagation.
releaseShared(int arg)
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}After successfully releasing the shared resource, the method invokes doReleaseShared() to wake up waiting threads.
Simple Application
Understanding AQS allows developers to implement custom synchronizers by providing implementations for:
isHeldExclusively() tryAcquire(int) tryRelease(int) tryAcquireShared(int) tryReleaseShared(int)The queue management, parking, and wake‑up logic are already handled by AQS.
Conclusion
Compared with exclusive locks, shared locks must wake up all subsequent shared nodes after a successful acquisition, because the resource can be held concurrently. When a shared lock is released, both shared and exclusive waiters may compete for the resource again.
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 Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
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.
