Fundamentals 32 min read

Deep Dive into Java's AbstractQueuedSynchronizer (AQS) and ReentrantLock

This article provides a comprehensive analysis of Java's AbstractQueuedSynchronizer (AQS) framework, explains how ReentrantLock is built on AQS, walks through lock acquisition and release mechanisms, discusses queue management, and demonstrates how to create a custom lock using AQS.

Qunar Tech Salon
Qunar Tech Salon
Qunar Tech Salon
Deep Dive into Java's AbstractQueuedSynchronizer (AQS) and ReentrantLock

Java's concurrency utilities rely heavily on the AbstractQueuedSynchronizer (AQS) framework, which offers a low‑level FIFO queue and atomic state management to implement various synchronizers such as ReentrantLock, ReentrantReadWriteLock, Semaphore, and CountDownLatch.

ReentrantLock Overview

ReentrantLock is a re‑entrant exclusive lock whose implementation is based on AQS. It supports both fair and non‑fair acquisition strategies, and its core methods— lock(), unlock(), tryLock() —delegate to AQS's acquire and release methods.

// Simplified ReentrantLock usage
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // critical section
} finally {
    lock.unlock();
}

AQS Core Concepts

AQS maintains a volatile int state representing the synchronization state and a doubly‑linked CLH‑style queue of Node objects. Each node holds a thread reference, a wait status, and links to predecessor and successor nodes.

private volatile int state; // synchronization state
static final class Node {
    volatile int waitStatus;
    volatile Node prev;
    volatile Node next;
    Thread thread;
    // ... other fields ...
}

Lock Acquisition Process

When a thread calls lock(), AQS executes acquire(1). If tryAcquire(1) fails, the thread is added to the wait queue via addWaiter(Node.EXCLUSIVE) and then repeatedly attempts to acquire the lock in acquireQueued. The algorithm checks whether the node is at the head of the queue; if so, it retries tryAcquire. Otherwise, it may park the thread using LockSupport.park() until it is unparked by a predecessor.

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

Queue Management and Cancellation

Nodes can enter a CANCELLED state when a thread gives up waiting or is interrupted. The cancelAcquire method removes cancelled nodes from the queue, updates predecessor and successor links, and may unpark the next waiting thread.

private void cancelAcquire(Node node) {
    if (node == null) return;
    node.thread = null;
    // Skip cancelled predecessors
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;
    // Adjust links and possibly unpark successor
    // ...
}

Lock Release Process

Calling unlock() invokes release(1), which delegates to the synchronizer's tryRelease. If the lock becomes fully released (state reaches zero), AQS unparks the successor of the head node.

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

Applications in the JUC Package

AQS underpins many Java concurrency utilities: ReentrantLock (exclusive lock), ReentrantReadWriteLock (shared/exclusive bits), Semaphore (permits count), CountDownLatch (countdown), and ThreadPoolExecutor (worker state). Each tool stores its specific state in the AQS state field and implements the required tryAcquire / tryRelease methods.

Creating a Custom Lock with AQS

The article demonstrates a minimal custom lock named LeeLock that extends AQS, providing simple exclusive acquisition and release logic.

public class LeeLock {
    private static final class Sync extends AbstractQueuedSynchronizer {
        @Override protected boolean tryAcquire(int arg) {
            return compareAndSetState(0, 1);
        }
        @Override protected boolean tryRelease(int arg) {
            setState(0);
            return true;
        }
        @Override protected boolean isHeldExclusively() {
            return getState() == 1;
        }
    }
    private final Sync sync = new Sync();
    public void lock()   { sync.acquire(1); }
    public void unlock() { sync.release(1); }
}

A test program runs two threads that each increment a shared counter 10,000 times under LeeLock, consistently producing the expected total of 20,000, illustrating correct mutual exclusion.

Conclusion

The article emphasizes that understanding AQS is essential for mastering Java's high‑performance concurrency utilities and for building custom synchronizers when needed.

References

Lea D. The java.util.concurrent synchronizer framework. Science of Computer Programming, 2005.

Java Concurrency in Practice.

不可不说的Java“锁”事 (WeChat article).

Author

Li Zhuo, R&D Engineer at Meituan's In‑Store Accommodation and Ticket Business team.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaconcurrencyLockAQSReentrantLock
Qunar Tech Salon
Written by

Qunar Tech Salon

Qunar Tech Salon is a learning and exchange platform for Qunar engineers and industry peers. We share cutting-edge technology trends and topics, providing a free platform for mid-to-senior technical professionals to exchange and learn.

0 followers
Reader feedback

How this landed with the community

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.