Java's AbstractQueuedSynchronizer, or AQS, is a powerful framework that serves as the foundation for building concurrency control tools in Java. It manages synchronization state using an integer variable and provides mechanisms for thread queuing and blocking. AQS is the backbone of many synchronization utilities in Java's concurrent package, including ReentrantLock, Semaphore, and CountDownLatch. It simplifies the implementation of these tools by handling the complex aspects of synchronization, such as managing waiting threads in a FIFO queue and providing efficient blocking mechanisms.
Let's examine the internal structure of AQS. At its core, AQS maintains an integer state variable that represents the synchronization state. This state is manipulated using atomic operations like Compare-And-Swap to ensure thread safety. AQS supports both exclusive mode, where only one thread can acquire the resource, and shared mode, where multiple threads can hold the resource simultaneously. The waiting threads are organized in a CLH queue, where each thread is represented by a Node object. Each Node contains a reference to the thread it represents, a wait status indicating the thread's condition, and links to the previous and next nodes in the queue. The AQS maintains head and tail pointers to manage this queue efficiently.
Let's explore how AQS handles thread acquisition and release. The acquire process starts when a thread calls the acquire method. First, it attempts to acquire the resource by calling tryAcquire. If successful, the thread returns immediately with the resource. If not, AQS creates a new node for the thread and enqueues it in the wait queue. The thread is then parked, meaning it's suspended until it's signaled. When the thread is later unparked, it tries again to acquire the resource. The release process begins when a thread calls release. It updates the synchronization state to reflect that the resource is available. Then it unparks the successor thread at the head of the queue. This successor thread wakes up and attempts to reacquire the resource. This elegant design ensures fairness and efficiency in resource management.
AQS serves as the foundation for many synchronization utilities in Java's concurrent package. Let's look at some common implementations. ReentrantLock uses AQS state to track the number of times the current thread has acquired the lock. Semaphore uses the state to represent the number of permits available. CountDownLatch uses state to track the count of remaining tasks before the latch opens. ReentrantReadWriteLock uses state bits to track both reader counts and writer holds. The implementation pattern is consistent: classes extend AbstractQueuedSynchronizer and implement the tryAcquire and tryRelease methods to define their specific synchronization logic. This pattern allows developers to create custom synchronizers by focusing on the core logic while AQS handles the complex threading mechanics.