The Condition interface provides a robust mechanism for inter-thread communication within synchronized blocks. Unlike traditional monitor methods, conditions offer finer-grained control over thread suspension and resumption, allowing multilpe independent wait sets to be associated with a single locking primitive.
Two core operations drive this coordination model:
| Operation | Behavior |
|---|---|
suspend |
Parks the executing thread and relinquishes the associated lock |
notify |
Resumes a waiting thread by transferring it back to the lock acquisition queue |
Suspension Routine
When a thread invokes the suspension method, it transitions into a waiting state. The implementation carefully manages lock release, queue enrollment, and interrupt handling to ensure thread safety and prevent lost wake-ups.
public final void suspend() throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
// Register the current thread into the condition wait list
QueueNode entry = allocateWaitNode();
int previousHoldCount = releaseAllLocks(entry);
int interruptStatus = 0;
// Spin until the node is successfully migrated to the main synchronization queue
while (!isEnqueuedForLock(entry)) {
ThreadSynchronizer.blockCurrentThread(this);
if ((interruptStatus = verifyInterruptState(entry)) != 0) {
break;
}
}
// Compete for lock ownership after being signaled
if (competeForLock(entry, previousHoldCount) && interruptStatus != THROW_EXCEPTION) {
interruptStatus = REASSERT_INTERRUPT;
}
// Purge cancelled or orphaned nodes from the condition queue
if (entry.getNextWaiter() != null) {
removeInvalidWaiters();
}
if (interruptStatus != 0) {
handlePostWaitInterrupt(interruptStatus);
}
}
The suspension routine begins by validating the thread's interrupt status. It then registers a new queue node, drops the exclusive lock, and enters a blocking loop. The thread remains parked via low-level primitives until a signal event occurs. Once transferred to the synchronization queue, it competes for lock ownership before safely returning to the caller.
Signaling Routine
Signaling strictly requires exclusive lock ownership. It locates the oldest waiting thread, detaches it from the condition queue, and prepares it for lock acquisition.
public final void notify() {
if (!ownsExclusiveLock()) {
throw new IllegalMonitorStateException();
}
QueueNode headWaiter = getFirstConditionNode();
if (headWaiter != null) {
transferAndWake(headWaiter);
}
}
private void transferAndWake(QueueNode initialNode) {
do {
// Update condition queue head and tail pointers
if ((firstConditionNode = initialNode.getNextWaiter()) == null) {
lastConditionNode = null;
}
initialNode.setNextWaiter(null);
} while (!migrateToSyncQueue(initialNode) &&
(initialNode = firstConditionNode) != null);
}
Queue Migration and Thread Resumption
The bridge between waiting and running states relies on atomic state updates and queue reorganization. The migration method ensures the target thread is safely inserted into the lock's synchronization queue before being unparked, preventing race conditions during concurrent signaling.
final boolean migrateToSyncQueue(QueueNode target) {
// Atomically transition node status from CONDITION to standard queue state
if (!atomicallyUpdateStatus(target, STATE_CONDITION, STATE_NORMAL)) {
return false;
}
QueueNode prev = enqueueForLock(target);
int predecessorStatus = prev.getStatus();
// If predecessor is cancelled or fails to signal its successor, unpark immediately
if (predecessorStatus > 0 || !atomicallySetStatus(prev, predecessorStatus, STATE_SIGNAL)) {
ThreadSynchronizer.unpark(target.getThread());
}
return true;
}
This atomic transfer guarantees that the awakened thread will eventually acquire the lock. The system carefully evaluates predecessor states to avoid missed wake-ups, ensuring deterministic thread resumption and mainatining fair lock acquisition ordernig.