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.
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 prevand 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.
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.
Thoughts on Knowledge and Action
Travel together, with knowledge and action all the way
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.
