Master Java's AbstractQueuedSynchronizer: Concepts, Mechanics, and Custom Implementations

This article explains the fundamentals of Java's AbstractQueuedSynchronizer (AQS), covering its basic concepts, core components, lock and unlock mechanisms, condition objects, and provides a pseudo‑code example for building a custom AQS implementation.

Xiaokun's Architecture Exploration Notes
Xiaokun's Architecture Exploration Notes
Xiaokun's Architecture Exploration Notes
Master Java's AbstractQueuedSynchronizer: Concepts, Mechanics, and Custom Implementations

AQS Basic Concepts and Functions

AQS stands for AbstractQueuedSynchronizer, a framework for building synchronizers.

Shared mode (shared lock/read lock) allows multiple threads to acquire the lock for reading.

Exclusive mode (exclusive lock/write lock) allows only one thread to hold the lock for writing.

Both modes share the same FIFO wait queue.

The state field holds synchronization state and is volatile with CAS for visibility and atomicity.

Main Functions of AQS

Provides a template framework for blocking locks and related synchronizers based on a FIFO wait queue.

Subclasses must define a non‑public helper class extending AQS and use its protected methods to implement lock behavior.

Subclasses rely on CAS operations on the state field and volatile semantics to maintain atomicity and visibility.

ReadWriteLock typically implements both shared and exclusive modes.

AQS defines an internal ConditionObject class that works with exclusive mode to enable thread communication via await/signal.

AQS Core Components

AQS class diagram
AQS class diagram

Key methods for using AQS subclasses: tryAcquire, tryRelease, tryAcquireShared, tryReleaseShared, isHeldExclusively, etc. Node inner class implements a double‑ended linked list for the wait queue, with fields prev, next, waitStatus, nextWaiter, thread, and mode.

static final class Node {
    static final Node SHARED = new Node(); // shared lock
    static final Node EXCLUSIVE = null; // exclusive lock
    static final int CANCELLED = 1; // cancelled
    static final int SIGNAL = -1; // signal
    static final int CONDITION = -2; // condition
    static final int PROPAGATE = -3; // propagate
    volatile int waitStatus;
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
    Node nextWaiter;
    Node(Thread thread, Node mode) {
        this.nextWaiter = mode;
        this.thread = thread;
    }
    Node(Thread thread, int waitStatus) {
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

ConditionObject

Implements java.util.concurrent.locks.Condition.

Maintains firstWaiter and lastWaiter for the condition queue.

Provides await, signal, signalAll and internal methods for managing waiters.

public class ConditionObject implements Condition, Serializable {
    private transient Node firstWaiter;
    private transient Node lastWaiter;
    private static final int REINTERRUPT = 1;
    private static final int THROW_IE = -1;
    // await, signal, signalAll implementations ...
    private Node addConditionWaiter() { /* ... */ }
    private void unlinkCancelledWaiters() { /* ... */ }
    private void doSignalAll(Node first) { /* ... */ }
}

AQS Core Attributes

state

– synchronization state; its meaning varies per subclass. exclusiveOwnerThread – thread holding the exclusive lock. Unsafe – low‑level CAS operations. LockSupport – utilities for parking and unparking threads.

Working Principle of AQS

Core properties: state, exclusiveOwnerThread, and a double‑ended wait queue (head & tail).

Exclusive Lock Interface

Template methods: acquire / release.

Implementation methods: tryAcquire / tryRelease.

Shared Lock Interface

Template methods: acquireShared / releaseShared.

Implementation methods: tryAcquireShared / tryReleaseShared.

Lock Acquisition

acquire

/ acquireShared define contention logic; if acquisition fails, the thread joins the wait queue. tryAcquire / tryAcquireShared perform the actual lock operation delegated to the concrete AQS subclass.

Lock Release

release

/ releaseShared define release logic and wake up the next waiting node. tryRelease / tryReleaseShared perform the actual unlock operation in the subclass.

Summary of AQS Lock/Unlock Process

Locking and unlocking rely on the waitStatus field; PROPAGATE indicates that the lock has been released and other threads may contend.

The double‑ended queue is manipulated via CAS to safely update head and tail.

Shared mode uses PROPAGATE to notify waiting shared nodes after release.

Locking can be shortcut by the subclass’s specific implementation.

Lock acquisition adds the node to the queue, CAS spins to acquire, then removes the node; unlocking wakes the next node.

Custom AQS

Key elements for a custom AQS implementation:

Thread‑safe double‑ended blocking queue.

Thread‑safe exclusive owner thread.

Thread‑safe state attribute.

Pseudo‑code for a custom AQS implementation:

// DefineAQS.java
public abstract class DefineAQS {
    final static class AQSNode {
        static final int SHARED = 9999;
        static final int EXCLUSIVE = -9999;
        private int mode;
        private volatile Thread thread;
        public AQSNode(int mode) {
            this.thread = Thread.currentThread();
            this.mode = mode;
        }
        public Thread getThread() { return thread; }
        public int getMode() { return mode; }
    }
    private AtomicInteger state = null;
    private AtomicReference<Thread> exclusiveOwnerThread = new AtomicReference<>();
    private LinkedBlockingQueue<AQSNode> waiters = new LinkedBlockingQueue<>();

    public AtomicInteger getState() { return state; }
    public void setState(int s) { this.state = new AtomicInteger(s); }
    public void compareAndSetState(int expect, int update) { this.state.compareAndSet(expect, update); }

    public void acquire(int arg) {
        AQSNode node = new AQSNode(AQSNode.EXCLUSIVE);
        waiters.offer(node);
        while (!tryAcquire(arg)) {
            LockSupport.park(node.getThread());
        }
        waiters.remove(node);
    }

    public void release(int arg) {
        if (tryRelease(arg)) {
            while (true) {
                AQSNode node = waiters.peek();
                if (node.getMode() == AQSNode.EXCLUSIVE) {
                    LockSupport.unpark(node.getThread());
                    break;
                }
            }
        }
    }

    public void acquireShared(int arg) {
        AQSNode node = new AQSNode(AQSNode.SHARED);
        waiters.offer(node);
        while (tryAcquireShared(arg) < 0) {
            LockSupport.park(node.getThread());
        }
        waiters.remove(node);
    }

    public void releaseShared(int arg) {
        if (tryReleaseShared(arg) > 0) {
            while (true) {
                AQSNode node = waiters.peek();
                if (node.getMode() == AQSNode.SHARED) {
                    LockSupport.unpark(node.getThread());
                    break;
                }
            }
        }
    }
    // abstract methods: tryAcquire, tryRelease, tryAcquireShared, tryReleaseShared
}
Custom AQS illustration
Custom AQS illustration
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.

JavaThreadSynchronizationLockAQS
Xiaokun's Architecture Exploration Notes
Written by

Xiaokun's Architecture Exploration Notes

10 years of backend architecture design | AI engineering infrastructure, storage architecture design, and performance optimization | Former senior developer at NetEase, Douyu, Inke, etc.

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.