Understanding Java's java.util.concurrent (J.U.C) Framework and AQS Mechanism
This article provides a comprehensive overview of Java's java.util.concurrent package, detailing its core components such as atomic classes, locks, collections, executors, tools, as well as an in‑depth explanation of the AbstractQueuedSynchronizer (AQS) framework, its fields, methods, and related concurrency utilities like FutureTask, BlockingQueue, ForkJoin, and work‑stealing algorithms.
J.U.C (java.util.concurrent) Overview
J.U.C is the core Java concurrency package providing many high‑performance concurrent classes.
J.U.C, CAS, Unsafe and AQS
All classes in java.util.concurrent rely on CAS operations supplied by sun.misc.Unsafe . The AbstractQueuedSynchronizer (AQS) framework underpins locks and synchronizers, using LockSupport.unpark() and LockSupport.park() for thread blocking and waking.
J.U.C Framework
The framework consists of five parts: tools, locks, collections, executor, and atomic.
Atomic
Contains atomic variable classes that depend only on Unsafe and are used by other modules.
Locks
Provides lock classes built on Unsafe . Understanding LockSupport , then the AQS framework, then the implementation of ReentrantLock (including exclusive and shared modes) is essential.
Collections
Includes concurrent collections such as ConcurrentHashMap , CopyOnWriteArrayList , CopyOnWriteArraySet , ArrayBlockingQueue , and LinkedBlockingQueue (the latter is important for thread‑pool implementations).
Executor
Focuses on thread‑pool execution, covering Callable , Future , FutureTask , ThreadPoolExecutor , and the four implementations of RejectedExecutionHandler . It also shows how to tune ThreadPoolExecutor parameters for optimal performance.
Tools
Covers advanced utilities such as CountDownLatch , CyclicBarrier , Semaphore , Exchanger , and the Executors factory methods. These are useful for coordinating multiple threads that depend on each other's results.
AQS Details
ReentrantLock
ReentrantLock is a re‑entrant exclusive lock; its implementation relies on an inner Sync class that extends AQS.
AQS Core
AQS maintains a volatile int state and a FIFO wait queue implemented as a doubly‑linked list. The queue head holds the current owner thread.
Key Fields
private transient volatile Node head; // head of the sync queue
private transient volatile Node tail; // tail of the sync queue
private volatile int state; // synchronization stateKey Methods
protected final int getState(); // returns the current state
protected final void setState(int newState); // sets the state
protected final boolean compareAndSetState(int expect, int update); // CAS update of stateacquire(int arg)
Attempts to acquire the resource via tryAcquire . If it fails, the thread is added to the wait queue and blocks until it can acquire.
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}addWaiter(Node mode)
Creates a new node for the current thread and tries to append it to the queue tail using CAS; if the queue is empty, it initializes a dummy head node.
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}enq(Node node)
CAS‑spins to insert the node at the tail, initializing the queue if necessary.
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
if (compareAndSetHead(new Node())) {
tail = head;
}
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}acquireQueued(Node node, int arg)
Loops while the node is not the head, checking if it can acquire; otherwise it may park the thread. Returns true if the thread was interrupted while waiting.
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}shouldParkAfterFailedAcquire(Node pred, Node node)
Determines whether the thread should block based on the predecessor’s wait status.
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}parkAndCheckInterrupt()
Calls LockSupport.park() and returns true if the thread was interrupted while parked.
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}Custom Synchronizer Resource Sharing
AQS supports exclusive and shared modes. Custom synchronizers need to implement methods such as isHeldExclusively() , tryAcquire(int) , tryRelease(int) , tryAcquireShared(int) , and tryReleaseShared(int) to define how the state is obtained and released.
Other J.U.C Components (to be detailed)
FutureTask
FutureTask implements RunnableFuture , allowing a Callable to be executed with a result that can be retrieved later.
public class FutureTask
implements RunnableFuture
{ ... }BlockingQueue
The java.util.concurrent.BlockingQueue interface has several implementations, including FIFO queues ( LinkedBlockingQueue , ArrayBlockingQueue ) and priority queues ( PriorityBlockingQueue ).
Producer‑Consumer Example
public class ProducerConsumer {
private static BlockingQueue
queue = new ArrayBlockingQueue<>(5);
// Producer thread puts "product" into the queue
// Consumer thread takes from the queue and processes it
// Main method starts multiple producers and consumers
}ForkJoin
The Fork/Join framework uses a divide‑and‑conquer approach. Tasks extend RecursiveTask and split themselves until the workload is below a threshold, then compute directly.
public class ForkJoinExample extends RecursiveTask
{
private final int threshold = 5;
private int first, last;
protected Integer compute() {
int result = 0;
if (last - first <= threshold) {
for (int i = first; i <= last; i++)
result += i;
} else {
int middle = first + (last - first) / 2;
ForkJoinExample left = new ForkJoinExample(first, middle);
ForkJoinExample right = new ForkJoinExample(middle + 1, last);
left.fork();
right.fork();
result = left.join() + right.join();
}
return result;
}
}Work‑Stealing Algorithm
In work‑stealing, idle threads steal tasks from other threads' deques to reduce contention. The stealing thread takes tasks from the tail, while the owning thread takes from the head. Java provides LinkedBlockingDeque and the ForkJoinPool that implements work‑stealing.
References:
https://blog.csdn.net/pange1991/article/details/80944797 https://blog.csdn.net/huzhiqiangcsdn/article/details/55251384 https://www.cnblogs.com/waterystone/p/4920797.html
Follow the public account for more interview questions and resources.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.