How AQS AbstractQueuedSynchronizer Powers Java Concurrency Primitives

AbstractQueuedSynchronizer (AQS) provides a framework that manages a FIFO wait-queue and an integer state variable to enable high-level Java concurrency primitives such as ReentrantLock, CountDownLatch, and Semaphore through exclusive and shared acquisition modes.

The java.util.concurrent (J.U.C) package relies heavily on AQS to implement its synchronization utilities. According to the concurrency notes in the CyC2018/CS-Notes repository, this framework abstracts the mechanics of blocking threads, queuing waiters, and atomic state management so that concrete classes can define only the logic for acquiring and releasing resources.

Core Architecture of AQS AbstractQueuedSynchronizer

The State Variable and CLH Queue

At the heart of AQS AbstractQueuedSynchronizer is a volatile int state field that represents the synchronization status. For a mutex, this might toggle between 0 and 1; for a semaphore, it tracks available permits. AQS organizes waiting threads into a CLH (Craig, Landin, and Hagersten) lock queue, a variant of a FIFO queue where each thread is encapsulated in a Node object.

When a thread fails to acquire the resource, AQS invokes acquire() or acquireShared(), creates a Node for the thread, appends it to the queue tail using CAS operations, and parks the thread using LockSupport.park(). This avoids busy-waiting and delegates thread scheduling to the JVM.

Acquisition and Release Protocols

The framework defines template methods that subclasses must implement:

  • tryAcquire(int arg): Attempts to acquire the exclusive resource. Returns true if successful.
  • tryAcquireShared(int arg): Attempts to acquire the resource in shared mode. Returns a non-negative value if successful.
  • tryRelease(int arg) and tryReleaseShared(int arg): Update the state to release the resource.

When release methods succeed, AQS automatically unparks the successor node in the CLH queue, allowing that thread to retry acquisition. This handshake between release() and acquire() ensures FIFO fairness while maintaining high throughput.

Exclusive vs. Shared Synchronization Modes

Exclusive Mode Implementation

In exclusive mode, only one thread may hold the resource at a time. ReentrantLock uses this pattern by extending AQS through an inner Sync class. When lock() is called, AQS invokes tryAcquire(1), which checks if the state is 0 (unlocked) and performs a CAS to 1. If the state is non-zero and the owner is the current thread, ReentrantLock increments the hold count to support reentrancy.

// ReentrantLock – exclusive mode built on AQS
Lock lock = new ReentrantLock();
Runnable task = () -> {
    lock.lock();                           // AQS attempts exclusive acquire via tryAcquire
    try {
        System.out.println(Thread.currentThread().getName() + " holds lock");
        Thread.sleep(500);
    } catch (InterruptedException ignored) {}
    finally {
        lock.unlock();                     // AQS releases state and unparks next waiter
    }
};
ExecutorService exec = Executors.newFixedThreadPool(3);
for (int i = 0; i < 3; i++) exec.execute(task);
exec.shutdown();

Shared Mode Implementation

Shared mode allows multiple threads to access the resource concurrently. Implementations like CountDownLatch and Semaphore override tryAcquireShared(int arg). In CountDownLatch, the initial state represents the count; tryAcquireShared returns 1 only when the state reaches 0, allowing all waiting threads to proceed simultaneously.

// CountDownLatch – uses AQS in shared mode
CountDownLatch latch = new CountDownLatch(3);
ExecutorService exec = Executors.newFixedThreadPool(3);
for (int i = 0; i < 3; i++) {
    exec.execute(() -> {
        System.out.println(Thread.currentThread().getName() + " doing work");
        latch.countDown();                 // releases a permit in AQS (decrements state)
    });
}
latch.await();                           // acquires shared permit (blocks until count == 0)
System.out.println("All work done");
exec.shutdown();

Semaphore uses shared mode to decrement permit counts. When acquire() is called, tryAcquireShared attempts to subtract 1 from the state; if the result is non-negative, the acquisition succeeds.

// Semaphore – AQS manages a permit counter
Semaphore sem = new Semaphore(2);          // at most 2 threads may proceed
ExecutorService exec = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
    exec.execute(() -> {
        try {
            sem.acquire();                // AQS tries to decrement state; may block if 0
            System.out.println(Thread.currentThread().getName() + " acquired");
            Thread.sleep(1000);            // simulate work
        } catch (InterruptedException e) { }
        finally {
            sem.release();                // AQS increments state and unparks waiting thread
        }
    });
}
exec.shutdown();

How J.U.C Primitives Use AQS AbstractQueuedSynchronizer

The java.util.concurrent primitives delegate synchronization logic to AQS through specialized inner classes. The following table maps each primitive to its AQS implementation details as documented in notes/Java 并发.md:

Primitive AQS Subclass Synchronization Mode State Management
ReentrantLock AbstractQueuedSynchronizer (inner Sync) Exclusive 0/1 state with reentrant hold count
CountDownLatch AbstractQueuedSynchronizer (inner Sync) Shared tryAcquireShared returns positive only when state == 0
Semaphore AbstractQueuedSynchronizer (inner Sync) Shared tryAcquireShared decrements available permits
CyclicBarrier AbstractQueuedSynchronizer (inner Sync) Shared tryAcquireShared returns 0 when all parties arrive

Each primitive overrides the template methods to define resource-specific logic while inheriting the robust queue management, cancellation handling, and interrupt response from AQS AbstractQueuedSynchronizer.

Summary

  • AQS AbstractQueuedSynchronizer provides the foundational framework for java.util.concurrent locks and synchronizers by managing a CLH FIFO queue and an integer state variable.
  • Subclasses implement tryAcquire/tryRelease for exclusive resources or tryAcquireShared/tryReleaseShared for shared resources, leaving queue mechanics to the framework.
  • Exclusive mode powers ReentrantLock, ensuring only one thread holds the lock at a time.
  • Shared mode enables CountDownLatch, Semaphore, and CyclicBarrier to allow multiple concurrent accessors.
  • The framework uses parking/unparking via LockSupport to block and wake threads efficiently without spin-waiting.

Frequently Asked Questions

What is the primary purpose of AQS AbstractQueuedSynchronizer?

AQS AbstractQueuedSynchronizer serves as a reusable synchronization framework that abstracts the complexity of blocking threads, managing wait queues, and performing atomic state updates. It allows developers to build custom locks and synchronizers by implementing only the resource-specific acquisition logic while inheriting robust queue management.

How does AQS AbstractQueuedSynchronizer handle thread blocking?

When a thread fails to acquire the resource, AQS encapsulates it in a Node and appends it to the CLH queue tail using CAS operations. The thread is then parked using LockSupport.park(), which removes it from the CPU scheduler until another thread calls release() and unparks the queue head via LockSupport.unpark().

What distinguishes exclusive mode from shared mode in AQS?

Exclusive mode, used by ReentrantLock, allows only one thread to hold the resource, and subsequent acquirers are queued. Shared mode, used by CountDownLatch and Semaphore, permits multiple threads to acquire the resource simultaneously; tryAcquireShared returns a non-negative value to signal success, and AQS propagates the release signal to subsequent nodes if the resource remains available.

Which Java classes extend AQS AbstractQueuedSynchronizer?

Core J.U.C classes including ReentrantLock (via inner class Sync), ReentrantReadWriteLock, CountDownLatch, Semaphore, and CyclicBarrier all extend AQS AbstractQueuedSynchronizer. Each implements the protected template methods to define custom synchronization policies while reusing the framework's queue infrastructure.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →