Fundamentals 17 min read

Understanding Java’s AbstractQueuedSynchronizer: Core Mechanisms and Custom Synchronizer Design

This article explains Java's AbstractQueuedSynchronizer (AQS) core architecture, including its state variable, CLH queue, condition queue, template methods, and the detailed implementations of acquire, release, and node management that enable building custom locks and synchronizers.

Thoughts on Knowledge and Action
Thoughts on Knowledge and Action
Thoughts on Knowledge and Action
Understanding Java’s AbstractQueuedSynchronizer: Core Mechanisms and Custom Synchronizer Design

Core Components of AQS

AbstractQueuedSynchronizer (AQS) is a fundamental JDK abstract class that provides a FIFO‑based queue to build locks, semaphores, read‑write locks, and other synchronizers. Subclasses such as ReentrantLock, Semaphore, ReentrantReadWriteLock, SynchronousQueue and FutureTask are built on top of AQS.

AQS maintains an int state field representing the synchronization state. Updates to this field are performed atomically via CAS:

/**
 * The synchronization state.
 */
private volatile int state;

/**
 * Atomically sets synchronization state to the given updated value
 * if the current state value equals the expected value.
 */
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

CLH Queue (Synchronization Queue)

Threads requesting a resource are wrapped into Node objects that form a CLH (Craig, Landin, and Hagersten) doubly‑linked queue. Each node records: thread: the actual thread mode: SHARED or

EXCLUSIVE
prev

and next: predecessor and successor waitStatus: one of CANCELLED (1), SIGNAL (-1), CONDITION (-2), PROPAGATE (-3)

Condition Queue (ConditionObject)

The condition queue holds threads that are waiting for a specific condition. Its key fields are firstWaiter and lastWaiter. The class implements await(), signal() and signalAll() methods.

public class ConditionObject implements Condition, java.io.Serializable {
    private transient Node firstWaiter;
    private transient Node lastWaiter;
    private static final int REINTERRUPT = 1;
    private static final int THROW_IE = -1;
}

Adding a new waiter:

private Node addConditionWaiter() {
    Node t = lastWaiter;
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

When a condition is signalled, its nodes are transferred to the synchronization queue.

AbstractOwnableSynchronizer

This abstract class provides setExclusiveOwnerThread(Thread) and getExclusiveOwnerThread() methods, allowing subclasses to record which thread currently holds an exclusive lock.

Resource Sharing Modes

Exclusive lock : only one thread can hold the lock; can be fair (FIFO) or non‑fair (immediate acquisition).

Shared lock : multiple threads may hold the lock concurrently (e.g., Semaphore, CountDownLatch).

Template Methods to Be Implemented by Subclasses

protected abstract boolean isHeldExclusively();
protected abstract boolean tryAcquire(int arg);
protected abstract boolean tryRelease(int arg);
protected abstract int tryAcquireShared(int arg);
protected abstract boolean tryReleaseShared(int arg);

All other methods in AQS are final and coordinate the queueing logic.

Acquisition Process

The public acquire(int arg) method follows three steps:

Attempt tryAcquire(arg). If it succeeds, the thread obtains the resource.

If it fails, call addWaiter(Node.EXCLUSIVE) to enqueue the thread.

Enter acquireQueued(node, arg), which repeatedly checks the predecessor node and parks the thread until it can acquire the lock.

public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

addWaiter implementation

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

enq implementation

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) {
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

acquireQueued logic

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);
    }
}

Parking decision

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

Parking is performed by:

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

Cancellation and Unparking

If a thread gives up, cancelAcquire(node) clears its references and updates predecessor links.

private void cancelAcquire(Node node) {
    if (node == null) return;
    node.thread = null;
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;
    Node predNext = pred.next;
    node.waitStatus = Node.CANCELLED;
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            unparkSuccessor(node);
        }
        node.next = node; // help GC
    }
}

Waking up a successor is handled by:

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

Release Process

The public release(int arg) method invokes tryRelease(arg) (implemented by the subclass). If it returns true, the head’s successor is unparked.

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

Subclasses must provide concrete implementations of tryRelease (and the other template methods) to define the actual lock semantics.

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.

JavaSynchronizationLocksthreadingAQS
Thoughts on Knowledge and Action
Written by

Thoughts on Knowledge and Action

Travel together, with knowledge and action all the way

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.