Java's AbstractQueuedSynchronizer (AQS) coordinates thread access to shared resources through a volatile integer state and a FIFO doubly-linked node structure. Concrete synchronizers built atop this framework include ReentrantLock, Semaphore, and CountDownLatch.
The framework maintains two distinct node chains: a synchronization queue for threads blocked while attempting to acquire a permit, and condition queues for threads parked while waiting for specific state changes.
public class RingBuffer<E> {
private static final int CAPACITY = 10;
private final Lock guard = new ReentrantLock();
private final Condition writable = guard.newCondition();
private final Condition readable = guard.newCondition();
private final Object[] buffer = new Object[CAPACITY];
private int writeIdx = 0;
private int readIdx = 0;
private int occupied = 0;
public void enqueue(E element) throws InterruptedException {
guard.lock();
try {
while (occupied == CAPACITY) {
writable.await();
}
buffer[writeIdx] = element;
writeIdx = (writeIdx + 1) % CAPACITY;
occupied++;
readable.signal();
} finally {
guard.unlock();
}
}
@SuppressWarnings("unchecked")
public E dequeue() throws InterruptedException {
guard.lock();
try {
while (occupied == 0) {
readable.await();
}
E result = (E) buffer[readIdx];
buffer[readIdx] = null;
readIdx = (readIdx + 1) % CAPACITY;
occupied--;
writable.signal();
return result;
} finally {
guard.unlock();
}
}
}
Condition queues are materialized by ConditionObject, an inner class that chains Node instances via nextWaiter to form a unidirectional list. The head and tail of this chain are tracked independently of the main sync queue.
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
private transient Node headWaiter;
private transient Node tailWaiter;
}
When a thread invokes await(), it appends itself to the condition list, fully releases the current synchronization state, and parks until signalled. Once awakened, it migrates back to the sync queue to reacquire the lock.
public final void await() throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
Node waiter = appendConditionWaiter();
long previousState = releaseFully(waiter);
int interruptStatus = 0;
while (!enqueuedOnSyncList(waiter)) {
LockSupport.park(this);
if ((interruptStatus = evaluateInterrupt(waiter)) != 0) {
break;
}
}
if (acquireQueued(waiter, previousState) && interruptStatus != THROW_IE) {
interruptStatus = REINTERRUPT;
}
if (waiter.nextWaiter != null) {
removeCancelledWaiters();
}
if (interruptStatus != 0) {
handleInterruptAfterWait(interruptStatus);
}
}
The signal() method transfers the first waiter from the conditino queue to the sync queue. It first verifies that the caller holds exclusive ownership, then dequeues and unparks the head node.
public final void signal() {
if (!ownsExclusiveLock()) {
throw new IllegalMonitorStateException();
}
Node head = headWaiter;
if (head != null) {
transferToSyncQueue(head);
}
}