AQS (AbstractQueuedSynchronizer) in Java

Introduction

AQS (AbstractQueuedSynchronizer) is a powerful tool in the Java concurrency package that provides a framework for implementing synchronization primitives. It is the foundation for many high-level concurrency utilities like locks, semaphores, and barriers. AQS uses a FIFO (First-In-First-Out) queue to manage threads waiting for a shared resource.

In this article, we will explore the basics of AQS and understand how it works. We will also look at a simple code example to demonstrate its usage.

Understanding AQS

AQS is an abstract class that provides methods for managing a synchronization state and a queue of waiting threads. It defines two main operations: acquire and release. The acquire operation is used by a thread to acquire the exclusive access to a resource, while the release operation is used to release the acquired resource.

AQS maintains the state of the synchronization object using a volatile variable called state. This state can represent different meanings depending on the specific synchronization primitive being implemented. For example, in a simple lock implementation, state can represent whether the lock is held or not.

The queue of waiting threads is managed using a doubly-linked list where each node represents a thread waiting for the resource. Threads are added to the tail of the queue when they try to acquire the resource and are removed from the head of the queue when the resource becomes available.

Code Example

Let's take a look at a simple code example that demonstrates the usage of AQS. We will implement a custom lock using AQS.

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class CustomLock {

    private static class Sync extends AbstractQueuedSynchronizer {
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        protected boolean tryAcquire(int acquires) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int releases) {
            if (getState() == 0)
                throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
    }

    private final Sync sync = new Sync();

    public void lock() {
        sync.acquire(1);
    }

    public void unlock() {
        sync.release(1);
    }
}

In the above code, we define a nested class Sync that extends AQS. This class overrides three methods: isHeldExclusively, tryAcquire, and tryRelease. These methods define the behavior of acquiring and releasing the lock.

The lock method simply calls the acquire method on the sync object, and the unlock method calls the release method.

Conclusion

AQS is a powerful tool in Java for implementing synchronization primitives. It provides a flexible and efficient framework for managing threads waiting for a shared resource. In this article, we explored the basics of AQS and demonstrated its usage through a simple code example.

Understanding AQS is important for building concurrent applications in Java. It is the underlying mechanism behind many higher-level synchronization utilities. By leveraging AQS, developers can create efficient and thread-safe code.