AQS ConditionObject Source Code Analysis

Keywords: Java JDK

AbstractQueued Synchronizer has one synchronization queue and several waiting queues. The types of nodes in the synchronization queue and waiting queue are both static internal class AbstractQueued Synchronizer. Node. The source code analyzed here is based on JDK 8.

ConditionObject includes the following fields

// Head node of waiting queue
private transient Node firstWaiter;
// End node of waiting queue
private transient Node lastWaiter;
// When interrupted before being notified, the interrupt mode is set to THROW_IE
private static final int REINTERRUPT =  1;
// When notified, set interrupt mode to REINTERRUPT
private static final int THROW_IE    = -1;

The first thing to say is the await() method. If a thread calls the Condition.await() method, the thread will release the lock, construct the node to join the waiting queue and enter the waiting state. When returned from the await() method, the current thread must have acquired the lock associated with Condition. If the await() method is viewed from a queue perspective, when the await() method is called, the first node of the synchronous queue (the node that acquires the lock) moves to the waiting queue of Condition.

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // Add the current thread to the waiting queue
    Node node = addConditionWaiter();
    // Release lock
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // The loop determines whether the node has been transferred to the synchronization queue. If the current node is not in the synchronization queue, it indicates that it has just been await. 
    // If no one calls the signal method, the current thread is suspended directly
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this); // Here the thread is suspended and stopped running
        // The ability to execute this indicates that either the signal() method has been invoked or the thread has been interrupted.
        // So check the reason why the thread is awakened, and if the interrupt is awakened, jump out of the while loop
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    // Delete cancelled successor nodes
    if (node.nextWaiter != null)
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

Source code for the addConditionWaiter() method

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If the tail node's state is not CONDITION, it is removed
    if (t != null && t.waitStatus != Node.CONDITION) {
        // Delete canceled nodes and reconnect
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    // Update tail node
    lastWaiter = node; 
    return node;
}

Source code for unlinkCancelledWaiters() method

private void unlinkCancelledWaiters() {
    // Record every node traversed
    Node t = firstWaiter;
    // Save the reference of the precursor node currently traversing the node because the waiting queue is a one-way queue
    Node trail = null;
    // Traveling from scratch
    while (t != null) {
        // Save references to successor nodes
        Node next = t.nextWaiter;
        // Delete if the current traversal node is not CONDITION
        if (t.waitStatus != Node.CONDITION) {
            t.nextWaiter = null;
            if (trail == null)
                firstWaiter = next;
            else
                trail.nextWaiter = next;
            if (next == null)
                lastWaiter = trail;
        }
        else
            trail = t;
        t = next;
    }
}

Here is the source code for the fullyRelease(Node node) method. You can see that the release(int arg) method is called in the fullyRelease(Node node) method, and the tryRelease(int arg) method is called inside the release(int arg) method.

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

Source code for isOnSyncQueue(Node node) method

final boolean isOnSyncQueue(Node node) {
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    if (node.next != null) // If has successor, it must be on queue
        return true;
    return findNodeFromTail(node);
}

// Search for whether the node is in the queue from the end of the synchronous queue
private boolean findNodeFromTail(Node node) {
    Node t = tail;
    for (;;) {
        if (t == node)
            return true;
        if (t == null)
            return false;
        t = t.prev;
    }
}

Source code for checkInterruptWhileWaiting(Node node) method

// Return THROW_IE interrupted before notification, REINTERRUPT interrupted after notification, and 0 if not interrupted.
private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}

final boolean transferAfterCancelledWait(Node node) {
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        enq(node);
        return true;
    }
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}

Source code of acquireQueued(final Node node, int arg) method

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final 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);
    }
}
private void reportInterruptAfterWait(int interruptMode)
    throws InterruptedException {
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    else if (interruptMode == REINTERRUPT)
        selfInterrupt();
}

static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

Posted by keegan on Sat, 06 Apr 2019 13:39:30 -0700