Backend Development 7 min read

Understanding Java's AbstractQueuedSynchronizer (AQS): Principles, Implementation, and Example

This article explains the core concepts of Java's AbstractQueuedSynchronizer, detailing its internal FIFO queue, state management, exclusive and shared modes, and provides complete code examples for custom lock implementation and usage.

Top Architecture Tech Stack
Top Architecture Tech Stack
Top Architecture Tech Stack
Understanding Java's AbstractQueuedSynchronizer (AQS): Principles, Implementation, and Example

AQS Overview

AQS (AbstractQueuedSynchronizer) is the foundational framework in Java for building locks and synchronizers, offering a simple abstract class that supports exclusive and shared synchronization modes.

Internal Data Structures

AQS maintains a FIFO wait queue implemented as a doubly‑linked list, where each node represents a thread waiting for the lock, and a volatile integer state variable that indicates the lock's current status.

Implementation Details

The state variable is 0 when the lock is free, a positive value when held by a thread, and a negative value when threads are waiting.

Exclusive Mode

In exclusive mode, AQS provides acquire and release methods to lock and unlock.

public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

The acquire method first attempts tryAcquire ; if it fails, the thread is enqueued and blocked until the lock is released, at which point the next waiting thread is unparked. The release method calls tryRelease and, on success, wakes the successor.

Shared Mode

Shared mode works similarly but allows multiple threads to hold the lock concurrently. The key methods are acquireShared and doAcquireShared :

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null;
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

When acquireShared cannot obtain the lock immediately, the thread is added to the wait queue and blocked until the lock becomes available.

Usage Example

The following example demonstrates a custom lock built on AQS:

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class MyLock extends AbstractQueuedSynchronizer {
    @Override
    protected boolean tryAcquire(int arg) {
        if (getState() == 0) {
            if (compareAndSetState(0, arg)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
        }
        return false;
    }

    @Override
    protected boolean tryRelease(int arg) {
        if (getState() == arg) {
            setState(0);
            setExclusiveOwnerThread(null);
            return true;
        }
        return false;
    }

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

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

And a simple test with two threads incrementing a counter:

public class Main {
    private static MyLock lock = new MyLock();
    private static int count = 0;

    public static void main(String[] args) {
        Runnable task = () -> {
            lock.lock();
            try {
                for (int i = 0; i < 10000; i++) {
                    count++;
                }
            } finally {
                lock.unlock();
            }
        };
        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("count: " + count);
    }
}

Conclusion

In summary, AQS provides an abstract lock mechanism in Java based on a wait/notify system, using a state variable and a FIFO queue to manage thread synchronization. It underlies many high‑level synchronizers such as ReentrantLock and Semaphore , and a solid understanding of its internals helps developers implement custom concurrency utilities effectively.

JavaconcurrencyLockAQSThread SynchronizationAbstractQueuedSynchronizer
Top Architecture Tech Stack
Written by

Top Architecture Tech Stack

Sharing Java and Python tech insights, with occasional practical development tool tips.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.