Understanding Java AQS, ReentrantLock, Fair vs Non‑Fair Locks, and Condition Implementation
This article provides a comprehensive walkthrough of Java's AbstractQueuedSynchronizer (AQS), its state and FIFO wait queue, the inner workings of ReentrantLock with both non‑fair and fair lock strategies, and the detailed mechanics of Condition objects with code examples and diagrams.
The article begins with an overview of concurrency in Java, introducing the AbstractQueuedSynchronizer (AQS) as the core abstraction that maintains a volatile int state representing the shared resource and a FIFO thread wait queue for blocked threads.
It explains how AQS operations such as compareAndSetState and UNSAFE.park() are used to acquire and release locks, and describes the internal fields like state , FIFO , and waitStatus that ensure visibility and ordering across threads.
Key methods of AQS are illustrated with source code snippets:
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}The article then details the step‑by‑step execution of three threads (Thread 1, Thread 2, Thread 3) competing for a lock, showing how successful acquisition, failure, and queue insertion are visualized with UML diagrams and images.
For lock release, the AQS release method is shown:
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 discussion then shifts to the difference between non‑fair and fair locks. Non‑fair locks allow a thread to acquire the lock immediately via CAS, potentially bypassing queued threads, while fair locks check hasQueuedPredecessors() to enforce FIFO ordering:
public final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}Fair locks thus provide true FIFO ordering at the cost of performance, while non‑fair locks offer higher throughput but may cause thread starvation.
Next, the article introduces the Condition interface introduced in Java 1.5, which replaces the traditional Object.wait() / notify() pattern with await() and signal() methods for safer and more efficient thread coordination.
Key AQS methods for Condition are presented, such as await() :
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}and signal() :
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}A complete demo program is provided, showing two threads where one acquires a ReentrantLock , calls condition.await() to release the lock and wait, while the other thread signals the condition, causing the first thread to resume:
public class ReentrantLockDemo {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
try {
System.out.println("Thread 1 locked");
System.out.println("Thread 1 awaiting");
condition.await();
System.out.println("Thread 1 resumed");
} catch (InterruptedException e) { e.printStackTrace(); }
finally { lock.unlock(); System.out.println("Thread 1 unlocked"); }
}).start();
new Thread(() -> {
lock.lock();
try {
System.out.println("Thread 2 locked");
condition.signal();
System.out.println("Thread 2 signaled");
} finally { lock.unlock(); System.out.println("Thread 2 unlocked"); }
}).start();
}
}The article concludes by summarizing that the detailed exploration of AQS, ReentrantLock (both fair and non‑fair), and Condition provides a deep understanding of Java's concurrency primitives, enabling developers to write correct multithreaded code and avoid common pitfalls such as deadlocks and thread starvation.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
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.