Unlocking Java Concurrency: A Deep Dive into AQS, Locks, and Condition Queues
This article explains Java's AbstractQueuedSynchronizer (AQS) framework, covering its MESA monitor model, entry and condition wait queues, exclusive and shared lock acquisition and release, and how core concurrency utilities like ReentrantLock, ReadWriteLock, CountDownLatch, Semaphore, ThreadPoolExecutor, and CyclicBarrier are built upon it.
Hello everyone, I am Su San.
In Java, AQS (AbstractQueuedSynchronizer) provides a FIFO queue‑based framework for implementing locks and related synchronizers such as semaphores and events.
AQS has two main responsibilities: manipulating the state variable and providing the queuing and blocking mechanisms.
1 Monitor Model
Java uses the MESA monitor model to protect class fields and methods, ensuring thread‑safety. The model defines a shared variable, a condition variable, and a condition‑wait queue.
MESA encapsulates the shared variable and its operations; a thread must acquire the lock before entering the monitor, otherwise it joins the entry‑wait queue.
Even after acquiring the lock, a thread may still wait in the condition‑wait queue if the condition is not satisfied.
When a thread is signalled from the condition‑wait queue, it must re‑enter the entry‑wait queue to try to obtain the lock again.
The AQS monitor model replaces the MESA model with a FIFO entry‑wait queue and a ConditionObject that can create multiple condition queues.
2 Entry‑Wait Queue
2.1 Acquire Exclusive Lock
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}Note: tryAcquire is abstract; concrete AQS subclasses implement it for their specific lock.
2.1.1 Enqueue
When lock acquisition fails, addWaiter creates a new node and appends it to the FIFO queue.
2.1.2 After Enqueue – Spinning to Acquire
The acquireQueued method repeatedly checks the node’s waitStatus and attempts to acquire the lock.
CANCELLED (1): node gave up.
SIGNAL (‑1): predecessor should wake this node.
CONDITION (‑2): used by ConditionObject.
PROPAGATE (‑3): shared mode propagation.
0: intermediate state.
2.1.3 Exclusive Lock with Interrupt
Method acquireInterruptibly(int arg) checks the thread’s interrupt status before trying to acquire the lock and also during the spin; if interrupted, it throws InterruptedException.
2.1.4 Exclusive Lock with Timeout
Method tryAcquireNanos(int arg, long nanosTimeout) adds timeout checks both during spinning and before parking the thread.
2.2 Release Exclusive Lock
Releasing consists of invoking the abstract tryRelease (implemented by subclasses) and then waking a successor node if the head is non‑null and its waitStatus is not zero.
2.3 Acquire Shared Lock
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
} tryAcquireSharedreturns:
negative – acquisition failed.
zero – acquisition succeeded but further shared acquires will fail.
positive – acquisition succeeded and further shared acquires may also succeed.
2.3.1 doAcquireShared
If tryAcquireShared returns a positive value, the thread becomes the new head and propagates the wake‑up to successors.
2.3.2 Shared Lock with Interrupt
Method acquireSharedInterruptibly(int arg) checks interrupt status before and during the spin, throwing InterruptedException if interrupted.
2.3.3 Shared Lock with Interrupt and Timeout
Method tryAcquireSharedNanos(int arg, long nanosTimeout) adds timeout checks similar to the exclusive version.
2.4 Release Shared Lock
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}After a successful tryReleaseShared, doReleaseShared spins to unpark successors while the queue has at least two nodes.
2.5 Summary
AQS uses a FIFO queue to implement entry‑wait queues and a state variable to drive exclusive and shared lock behavior, forming the basis for many Java concurrency utilities.
3 Concurrency Locks Built on AQS
3.1 ReentrantLock
ReentrantLock’s internal Sync class extends AQS. It implements tryAcquire, tryRelease, and isHeldExclusively. The lock can be fair or non‑fair; fair locks always queue, while non‑fair locks try CAS first.
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}3.1.1 Fair vs Non‑Fair
Fair lock checks for a predecessor node before CAS; non‑fair lock attempts CAS immediately.
3.2 ReentrantReadWriteLock
Uses a single Sync that implements both exclusive and shared methods, allowing concurrent reads and exclusive writes.
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }3.2.1 Read Lock
ReadLock calls acquireShared(1). The sync checks that no exclusive lock is held and that the shared count is below the maximum before incrementing the shared portion of state.
3.2.2 Write Lock
WriteLock calls acquire(1). It succeeds if state is zero or if the current thread already holds the exclusive lock (re‑entrancy).
3.3 CountDownLatch
CountDownLatch’s internal Sync extends AQS with a shared state initialized to the count. await uses acquireSharedInterruptibly(1); countDown uses releaseShared(1).
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}3.4 Semaphore
Semaphore also extends AQS; its state holds the number of permits. acquire uses acquireSharedInterruptibly(1), and release uses releaseShared(1). It can be fair or non‑fair.
3.5 ThreadPoolExecutor
ThreadPoolExecutor uses an internal Worker class that implements its own exclusive lock via AQS to protect state changes when interrupting workers.
4 Condition‑Variable Wait Queues
4.1 Official Example
public class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally { lock.unlock(); }
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally { lock.unlock(); }
}
}The two conditions notFull and notEmpty represent the two wait queues used by the buffer.
4.2 Principle
Condition objects are built on top of AQS. They maintain a singly‑linked wait queue (different from the doubly‑linked entry‑wait queue). Nodes in the condition queue have waitStatus = -2.
4.3 await
Calling await performs three steps: (1) add the thread to the condition queue, (2) fully release the associated lock (setting state to 0), and (3) block the thread until it is signalled.
4.4 signal / signalAll
signalmoves the first waiting node whose waitStatus == -2 from the condition queue to the entry‑wait queue, where it will attempt to reacquire the lock. signalAll transfers all waiting nodes.
4.5 Usage Example – CyclicBarrier
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(2, () -> {
System.out.println("All parties arrived");
});
ExecutorService exec = Executors.newFixedThreadPool(2);
exec.submit(() -> {
try { System.out.println("Thread 1"); barrier.await(); }
catch (Exception e) { e.printStackTrace(); }
});
exec.submit(() -> {
try { System.out.println("Thread 2"); barrier.await(); }
catch (Exception e) { e.printStackTrace(); }
});
exec.shutdown();
}Each thread calls await on the barrier’s internal condition; when the last thread arrives, the barrier signals all waiting threads.
4.6 Summary
Java’s monitor model follows the MESA paradigm. AQS supplies a FIFO entry‑wait queue, a state variable for exclusive/shared lock semantics, and ConditionObject for condition‑wait queues. All major concurrency utilities in java.util.concurrent are built on this foundation.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
