Unlocking Java AQS Shared Mode: How Semaphore Works Under the Hood
This article explains the shared‑mode acquisition in Java's AbstractQueuedSynchronizer, walks through the core template methods, compares them with exclusive mode, and demonstrates a classic application by dissecting the Semaphore source code and its usage patterns.
Preface
The previous long article on Java AQS queue synchronizer and ReentrantLock laid the groundwork for reading JUC source code; this piece focuses on the shared‑mode acquisition, so readers are encouraged to review the earlier article if needed.
Shared‑Mode Acquisition in AQS
Unlike exclusive mode where only one thread can hold the lock, shared mode allows multiple threads to acquire the synchronization state simultaneously. The key difference is whether multiple threads can obtain the state at the same moment.
Can multiple threads acquire the synchronization state at the same time?
The synchronization state state is maintained inside AQS. The shared‑mode template methods control state similarly to exclusive mode, but they also propagate the state to waiting threads.
Source Code Analysis of Shared‑Mode in AQS
Key methods from the previous article are highlighted (shown in pink) to help recall the shared‑mode flow.
Methods that a custom synchronizer must override
AQS template methods
Key shared‑mode code (with comments) is shown below:
public final void acquireShared(int arg) {<br/> // Non‑blocking attempt to acquire shared state; if result < 0, acquisition fails<br/> if (tryAcquireShared(arg) < 0)<br/> // Enter waiting queue via template method<br/> doAcquireShared(arg);<br/>}Entering doAcquireShared:
private void doAcquireShared(int arg) {<br/> // Create a SHARED node and add it to the wait queue<br/> final Node node = addWaiter(Node.SHARED);<br/> boolean failed = true;<br/> try {<br/> boolean interrupted = false;<br/> for (;;) {<br/> final Node p = node.predecessor();<br/> if (p == head) {<br/> int r = tryAcquireShared(arg);<br/> if (r >= 0) {<br/> setHeadAndPropagate(node, r);<br/> p.next = null; // help GC<br/> if (interrupted) selfInterrupt();<br/> failed = false;<br/> return;<br/> }<br/> }<br/> if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())<br/> interrupted = true;<br/> }<br/> } finally {<br/> if (failed) cancelAcquire(node);<br/> }<br/>}The crucial difference from exclusive mode lies in the setHeadAndPropagate(node, r) method, which not only sets the head but also propagates the remaining state to other waiting threads.
private void setHeadAndPropagate(Node node, int propagate) {<br/> Node h = head;<br/> setHead(node);<br/> if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) {<br/> Node s = node.next;<br/> if (s == null || s.isShared())<br/> doReleaseShared();<br/> }<br/>}The propagation logic ensures that when a thread successfully acquires the state and leaves a positive remainder, waiting threads are notified to try acquiring the remaining permits.
Further analysis of doReleaseShared() shows how the head node’s waitStatus is examined and how waiting successors are unparked.
private void doReleaseShared() {<br/> for (;;) {<br/> Node h = head;<br/> if (h != null && h != tail) {<br/> int ws = h.waitStatus;<br/> if (ws == Node.SIGNAL) {<br/> if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue;<br/> unparkSuccessor(h);<br/> } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) {<br/> continue;<br/> }<br/> if (h == head) break;<br/> }<br/> }<br/>}With the shared‑mode mechanics clarified, the article moves to a classic application: Semaphore .
Semaphore Concept
Semaphore (信号量) can be thought of as a traffic light with two flags: red (stop) and green (go). In Java, tryAcquireShared(arg) returning a non‑negative value means the light is green (permit acquired), otherwise it is red.
Semaphore Source Code Analysis
The class structure mirrors that of ReentrantLock, with a non‑fair synchronizer by default.
Construction sets the initial state (permits) in the AQS super class.
public Semaphore(int permits) {<br/> sync = new NonfairSync(permits);<br/>}<br/><br/>static final class NonfairSync extends Sync {<br/> NonfairSync(int permits) {<br/> super(permits);<br/> }<br/>}When permits == 1, Semaphore behaves like a simple mutex, but it can also be used for rate‑limiting by setting a larger permit count.
static int count;<br/>static final Semaphore s = new Semaphore(1);<br/>static void addOne() {<br/> s.acquire();<br/> try {<br/> count += 1;<br/> } finally {<br/> s.release();<br/> }<br/>}In practice, for high‑performance rate limiting, Guava's RateLimiter is often preferred.
Summary
By linking the shared‑mode template methods with concrete examples like Semaphore, the article shows how to read JUC source code without getting lost, preparing readers for further topics such as ReadWriteLock and CountDownLatch.
Soul‑Searching Questions
When permits is set to 1, Semaphore resembles a simple mutex—what are the key differences compared to ReentrantLock?
How have you used Semaphore in your own projects?
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.
