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.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Deep Dive into Java AQS Shared‑Lock Implementation

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaconcurrencySynchronizationmultithreadingAQSSharedLock
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.