What is AbstractQueuedSynchronizer (AQS) and why does it… — Cracked Java
SeniorTheoryBig TechGoogle

What is AbstractQueuedSynchronizer (AQS) and why does it matter?

AbstractQueuedSynchronizer (AQS) is the framework underneath almost every synchronizer in java.util.concurrent: ReentrantLock, ReentrantReadWriteLock, Semaphore, CountDownLatch, ThreadPoolExecutor's worker, and FutureTask. It centralizes the hard parts — atomic state, the wait queue, and parking/unparking — so each lock only implements a tiny policy on top.

The two pieces

  • A single volatile int state manipulated only via CAS (compareAndSetState). Each synchronizer interprets this integer:
    • ReentrantLock: 0 = free, n = hold count.
    • Semaphore: number of available permits.
    • CountDownLatch: the remaining count.
    • ReentrantReadWriteLock: writer count in the low 16 bits, reader count in the high 16 bits of the same int.
  • A FIFO CLH-style wait queue of nodes. When a thread fails to acquire, AQS enqueues it and LockSupport.park()s it; a releasing thread unparks the head's successor.

The template-method design

Subclasses don't touch the queue. They override a few hooks and call AQS's acquire/release engine:

// Sketch of a non-reentrant mutex on top of AQS
static class Mutex extends AbstractQueuedSynchronizer {
    @Override protected boolean tryAcquire(int arg) {
        return compareAndSetState(0, 1);          // try to flip free -> held
    }
    @Override protected boolean tryRelease(int arg) {
        setState(0);                              // mark free
        return true;
    }
    @Override protected boolean isHeldExclusively() { return getState() == 1; }
}
// acquire(1) -> tryAcquire; on failure, enqueue + park.
// release(1) -> tryRelease; on success, unpark successor.

tryAcquire/tryRelease (exclusive) and tryAcquireShared/tryReleaseShared (shared, e.g. read lock / Semaphore permits) are the only policy; AQS handles queuing, parking, interruption, timeouts, and fairness.

Why it matters in an interview

It explains the family resemblance across all these classes, why they share fairness/tryLock/timed semantics, and how fairness works (hasQueuedPredecessors() consults the queue). It also explains the cost model: contention turns into CAS retries plus park/unpark, which is why low-contention barging beats strict FIFO.

Mark your status