Understanding CAS, AQS, and Fair vs Non‑Fair Locks in Java
This article explains the principles of CAS, the ABA problem, and how Java's AbstractQueuedSynchronizer underpins ReentrantLock, comparing fair and non‑fair lock implementations, with code examples and discussion of their performance trade‑offs in.
CAS (Compare‑And‑Swap) is a low‑level atomic primitive that compares a current memory value with an expected value and, if they match, swaps it with a new value. The three values involved are the new value, the current memory value, and the expected (old) value. Java's AtomicInteger uses CAS to implement atomic operations, as shown in the example:
// similar to a global shared variable V
AtomicInteger atomicInteger = new AtomicInteger(0);
// CAS operation
boolean b = atomicInteger.compareAndSet(0, 1);
System.out.println("Modification success:" + b);
System.out.println("New value:" + atomicInteger.get());The ABA problem occurs when a variable changes from value A to B and back to A; a thread that only checks for equality cannot detect the intermediate change. Adding a version number to each update solves this issue by allowing threads to detect any modification.
AQS (AbstractQueuedSynchronizer) is the core framework in the JDK for building synchronizers such as ReentrantLock , CountDownLatch , and others. It manages a FIFO wait queue of threads and provides the low‑level CAS‑based state management.
In ReentrantLock , the lock operation delegates to an internal Sync object. The source code looks like:
public void lock() {
sync.lock();
}The Sync class is a static subclass of AbstractQueuedSynchronizer . Two concrete subclasses implement the lock behavior: FairSync (fair lock) and NonfairSync (non‑fair lock). The default constructor creates a non‑fair lock:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}A fair lock grants access to threads in the order they arrived, maintaining a strict FIFO queue. When the lock is released, only the next thread in the queue is unparked, ensuring no thread starvation but incurring higher context‑switch overhead.
A non‑fair lock allows a thread to attempt to acquire the lock immediately; if it succeeds, it bypasses the queue, reducing wake‑up overhead and improving throughput. However, threads waiting longer in the queue may experience starvation.
The AQS double‑linked queue stores nodes with prev and next pointers, simplifying removal of cancelled nodes (e.g., when a thread is interrupted) and enabling efficient wake‑up of successor threads without traversing the entire list.
Overall, the article provides a detailed explanation of how CAS underlies atomic classes, how the ABA problem is mitigated, and how AQS implements both fair and non‑fair locking strategies in Java, highlighting their trade‑offs.
IT Architects Alliance
Discussion and exchange on system, internet, large‑scale distributed, high‑availability, and high‑performance architectures, as well as big data, machine learning, AI, and architecture adjustments with internet technologies. Includes real‑world large‑scale architecture case studies. Open to architects who have ideas and enjoy sharing.
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.