Detailed java lock queue synchronizer AQS

Keywords: less Java JDK

1. Introduction to AQS

AQS (java.util.concurrent.locks.AbstractQueuedSynchronizer) is the basic framework class used to construct locks or other synchronization components (semaphores, events, etc.).The internal implementation of many concurrent tool classes in JDK relies on AQS, such as ReentrantLock, Semaphore, CountDownLatch, and so on.

The main way to use AQS is to inherit it as an internal auxiliary class to implement the synchronization primitive, which simplifies the internal implementation of your concurrent tools, blocking the underlying operations such as synchronization state management, thread queuing, waiting and waking.

In AQS-based synchronizer classes, the most basic operations include various forms of get and release operations.The get operation is a dependent state operation and is usually blocked.When using locks or semaphores, the meaning of the "get" operation is intuitive, that is, the lock or license is acquired, and the caller may wait until the synchronizer class is accessible.If a class wants to become a state-dependent class, it must have some states.AQS manages the state in the Synchronizer class, which manages an integer state information that can be manipulated using protected type methods such as getstate, setState, and compareAndSetState.This integer can be used to represent any state.

AQS mainly does three things:

  • Management of Synchronization State
  • Thread Blocking and Wakeup
  • Maintenance of synchronization queues

Status Management API:

  • int getState(): Get synchronization status
  • void setState(): Set the synchronization state
  • boolean compareAndSetState(int expect, int update): based on CAS, atomic setting state

AQS template method:

Method describe
void acquire(int arg) Get synchronization state exclusively. If the current thread gets synchronization state successfully, it is returned by this method. Otherwise, it will enter the synchronization queue and wait, which will call the overridden tryAcquire(intarg) method
void acquireInterruptibly(int arg) Same as acquire(int arg), but this method responds to interrupts, the current thread does not get the synchronization state and enters the synchronization queue. If the current thread is interrupted, this method throws InterruptedException and returns
boolean tryAcquireNanos(int arg,long nanos) A timeout limit is added to acquireInterruptibly(int arg), which returns false if the current thread does not get synchronization within the timeout and true if it does
void acquireShared(int arg) Shared fetch synchronization status, if the current thread does not get synchronization status, will enter the synchronization queue waiting, the main difference from exclusive fetch is that multiple threads can get synchronization status at the same time.
void acquireSharedInterruptibly(int arg) Like acquireShared(int arg), this method responds to interruptions.
boolean tryAcquireSharedNanos(int arg,long nanos) Timeout limits have been added to acquireSharedInterruptibly(int arg).
boolean release(int arg) Exclusive release synchronization state, which wakes up the thread contained by the first node in the synchronization queue after release synchronization state.
boolean releaseShared(int arg) Shared release synchronization state
Collection getQueuedThreads() Gets the collection of threads waiting on the synchronization queue

Synchronizer overridable methods:

Method describe
boolean tryAcquire(int arg) Getting synchronization status exclusively requires querying the current status and determining if it is in the expected state before CAS sets the synchronization status.
boolean tryRelease(int arg) Exclusive release of synchronization state, threads waiting to get synchronization state will have the opportunity to get synchronization state
int tryAcquireShared(int arg) Shared Get Synchronization Status returns a value greater than or equal to 0 indicating success or failure
boolean tryReleaseShared(int arg) Shared Release Synchronization Status
boolean isHeldExclusively() Whether the current synchronizer is occupied by a thread in exclusive mode, which is a general way of indicating whether it is exclusive by the current thread

Exposure methods for AQS:

Exposure methods.png

2. AQS data structure

Since acquiring locks is conditional, threads that do not acquire locks will block waiting, and those waiting threads will be stored.The CLH queue is used in AQS to store these waiting threads, but it does not store the threads directly, it stores the node node that owns them.

2.1. Node data structure:

static final class Node {
    //Flag for shared mode, identifying a node waiting in shared mode
    static final Node SHARED = new Node();
    //Marker for exclusive mode, identifying a node waiting in exclusive mode
    static final Node EXCLUSIVE = null;

    // The value of the waitStatus variable, which indicates that the thread was cancelled and that no locks will be acquired later
    static final int CANCELLED = 1;
     // The value of the waitStatus variable, which indicates that subsequent threads (that is, nodes behind this node in the queue) need to be blocked. (Used for exclusive locks)
    static final int SIGNAL = -1;
    // The value of the waitStatus variable, which indicates that the thread is waiting for blocking on the Condition condition. (await wait for Condition)
    static final int CONDITION = -2;
    // The value of the waitStatus variable indicates that the next acquireShared method thread should be propagated unconditionally.(for shared locks)
    static final int PROPAGATE = -3;

     // Marks the state of the current node. The default state is 0. States less than 0 have a special effect. States greater than 0 indicate cancellation.
     //SIGNAL: The successor node of this node is blocked, so the thread of the successor node needs to be replaced when the node releases a lock or interrupt.
     //CANCELLED: The node state is set to this state when the acquisition lock timeout or interruption occurs, and the node in this state will not be blocked again;
     //CONDITION: Identifies that this node is in a conditional queue and that this state does not occur on a node in a synchronous queue.
     //This state is set to 0 when a node moves from a conditional queue to a synchronous queue.
     //PROPAGATE: A releaseShared should be propagated to other nodes, this state is called in doReleaseShared(),
     //Ensure that propagation continues when other inserts occur.
     //Summary: A status of less than 0 means the node does not need to be notified to wake up; a status of 0 means the normal synchronization node; and CONDITION means the node is in
     //In the wait queue, the state is updated atomically by CAS.
    volatile int waitStatus;

    /**
     * Precursor node, set at enqueue, cleared when dequeue or precursor node cancels;
     */
    volatile Node prev;

    /**
     * Subsequent nodes, set at enqueue, cleared when dequeue or predecessor node cancels;
     * The queuing operation does not set the successor nodes of the precursor node until the node is connected to the queue;
     * So a null next node does not necessarily mean that it is the end of the queue. When the next node is null,
     * prev nodes can be traversed for double-checking; the next s of canceled nodes point to themselves instead of null
     */
    volatile Node next;

    //Threads owned by this node
    volatile Thread thread;

    /**
     * 1,Value is null or non-SHARED; null indicates exclusive mode; non-SHARED indicates queue waiting in Condition;
     * 2,The value is SHARED, indicating the shared mode;
     */
    Node nextWaiter;

    //Is Shared Mode
    final boolean isShared() {
        return nextWaiter == SHARED;
    }
    //The precursor node of the current node, throws an NPE exception if the precursor node is null
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {    // Used to establish initial head or SHARED marker
    }

    //Used in addWaiter(), to create nodes in a synchronization queue
    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }

    //Used in Condition s, create a node to wait for a queue
    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

Explain:

  • waitStatus: Represents the status of the current node and has the following five states:

CANCELLED (1): When the acquisition of a lock is timed out or interrupted, the node state is set to this state, the node in this state is unpark, will not participate in acquiring the lock, will not be blocked again;

0: Represents the state of a normal node when it is initially inserted into a synchronization queue;

SIGNAL (-1): The successor node of this node is blocked, so the thread of the successor node needs to be replaced when the node releases a lock or interrupt.

CONDITION (-2): Identifies that this node is in a conditional queue and that this state does not occur at the node in the synchronization queue, which is set to 0 when the node moves from the conditional queue to the synchronization queue;

PROPAGATE (-3): A release Shared should be propagated to other nodes, and this state is called in doReleaseShared() to ensure that the propagation continues on other inserts.

Summary: A status of less than 0 means that the node does not need to be notified to wake up; a status of 0 means a normal synchronization node; and a CONDITION means that the node is in a waiting queue and the state is updated atomically through CAS.

  • prev: the precursor node, set at enqueue, cleared when dequeue or precursor node cancels;
  • Next: the succeeding node, set at enqueue, cleared when dequeue or precursor node cancels; the queuing operation does not set the succeeding node of the precursor node until the node is connected to the queue; therefore, a null next node does not necessarily mean that the node is at the end of the queue; when the next node is null, the prev node can be traversed for double checking; the next of the canceled node points to itself instead of null
  • **nextWaiter:**Value is SHARED, indicating shared mode; value is null or non-SHARED, indicating exclusive mode when null, and node waiting in Condition queue when non-SHARED;

WatStatus status:

State Transition.png

2.2, AQS data structure

/**
 * Wait for the head node of the queue to be updated by the setHead method; the state of the head node cannot be CANCELLED
 */
private transient volatile Node head;

/**
 * Waiting for the end node of the queue
 */
private transient volatile Node tail;

/**
 * Synchronization status
 */
private volatile int state;

3. CLH Queue Related Operations

3.1. CAS operations for related attributes

/**
 * Setting the head value of AQS through CAS
 */
private final boolean compareAndSetHead(Node update) {
    return unsafe.compareAndSwapObject(this, headOffset, null, update);
}

/**
 * Setting tail value of AQS through CAS
 */
private final boolean compareAndSetTail(Node expect, Node update) {
    return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}

/**
 * Set waitStatue value of Node node through CAS
 */
private static final boolean compareAndSetWaitStatus(Node node,
                                                     int expect,
                                                     int update) {
    return unsafe.compareAndSwapInt(node, waitStatusOffset,
            expect, update);
}

/**
 * Setting the next value of Node node through CAS
 */
private static final boolean compareAndSetNext(Node node,
                                               Node expect,
                                               Node update) {
    return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}

3.2, Add Node to CLH End

//Insert a node into the end of the synchronization queue by idling and CAS
private Node enq(final Node node) {
    for (; ; ) {
        Node t = tail;
        
        //If the tail node is empty, CAS initializes the header node directly and sets the tail node as the header node
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            //If the tail node is not empty, CAS sets the current node as the tail node
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

3.3. Add the current thread to the end of the CLH queue

//Creates a node of a given pattern with the current thread and joins the node to the end of the queue
private Node addWaiter(Node mode) {
    //Create Threads
    Node node = new Node(Thread.currentThread(), mode);
    //Fast Detection Tail Node is not empty, CAS replaces the current node with the tail node
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //Fast replacement failed?Then insert the current node into the end of the queue by emptying enq()
    enq(node);
    return node;
}

4. Exclusive Lock

Exclusive locks have two main functions:

  • The ability to acquire locks: Since only one thread can acquire locks when multiple threads acquire locks together, other threads must block waiting at the current location.
  • Function to release locks: Threads that acquire locks release lock resources and must be able to wake up a thread that is waiting for a lock resource.

4.1. Exclusive Lock Acquisition Process

Get exclusive lock process.png

4.2. Approaches to acquiring exclusive locks

//Acquire exclusive locks, ignoring interrupts; this method is often called by lock.lock until a lock is successfully acquired
public final void acquire(int arg) {
    //tryAcquire: CAS tries to acquire the lock first, and when true is returned it indicates success, this method is implemented by subclasses;
    //When false is returned, it is called
    //acquireQueued handles acquisition and queue blocking and wakes up altered threads when other threads release locks.
    if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

//Call tryAcquire to attempt to acquire the lock, queue the thread node and block the node thread if the acquisition fails;
//Until the thread is interrupted or waked up, it will try to acquire the lock again;
final boolean acquireQueued(final Node node, int arg) {
    //Identity acquisition lock failed
    boolean failed = true;
    try {
        //Identity lock interrupted
        boolean interrupted = false;
        for (; ; ) {
            //Gets the precursor node of the current node, and if the precursor node is the head node, attempts to acquire the lock;
            //Set the current node as the head node if successful, otherwise try to block the node thread
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                //Set the current node as the head node
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //Determines whether the blocking condition is currently satisfied or blocks the current thread if it is satisfied;
            //And wait for interruption or wakeup
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        //Cancel acquisition if lock acquisition fails
        if (failed)
            cancelAcquire(node);
    }
}
//Determine if the current node thread needs to be blocked
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //Status of the precursor node
    int ws = pred.waitStatus;
    
    //The state of the precursor node is SIGNAL, indicating that the precursor node is waiting for a signal to acquire a lock
    //So this node can be safely blocked
    if (ws == Node.SIGNAL)
        return true;
    
    //The precursor node waitStatus>0, that is, waitStatus=CANCELLED;
    //Indicates that the precursor node has been cancelled and needs to be traversed forward until it is in state
    //Not a node of CANCELLED, and set this node as the precursor node of the node;
    //Returns false, letting the upper call continue trying to acquire the lock
    if (ws > 0) {
        //Loop through the precursor nodes to find nodes that are not in the CANCELLED state and set to the current node's
        //Precursor Node
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        //Setting the precursor node state to SIGNAL through CAS when the current precursor node state is 0 or PROPAGATE
        //And return to fase, waiting for the next loop to block the current node thread;
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

//Block the current thread through LockSupport.park() until it is unpark ed or interrupted
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

4.3. Release exclusive lock related methods

//Release exclusive lock
public final boolean release(int arg) {
    //Call tryRelease to attempt to release the lock via CAS, which is implemented by subclasses
    if (tryRelease(arg)) {
        Node h = head;
        
        //Head Node is not empty and Head Node Status is not zero, should be SIGNAL
        //Represents a node in the queue that needs to be waked up, and calls unparkSuccessor for a header node thread
        //Wake-up operation
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
//Wake up node thread processing
private void unparkSuccessor(Node node) {
    //Get the current node state
    int ws = node.waitStatus;
    //If the state is less than zero, the state is reset to zero, indicating that node processing is complete
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    //Gets the successor node when the successor node is empty or the successor node state is CANCELLED;
    //Traverse the queue forward by tail to find the next valid node of the current node, waitStatus <= 0
    //Node
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    //Next node is not empty, representing a node waiting for a signal, executing unpark wake-up node thread
    if (s != null)
        LockSupport.unpark(s.thread);
}

5. Shared Lock

5.1. Shared lock acquisition process

Shared lock acquisition process.png

5.2. Shared lock acquisition related methods

//Acquire shared lock, ignore interrupt;
public final void acquireShared(int arg) {
    //Subclass implementation, CAS method to acquire shared locks, if acquisition fails, call doAcquireShared to continue acquiring shared locks
    if (tryAcquireShared(arg) < 0)
        //Attempt to acquire shared lock, failure to acquire queues current thread node until notified or interrupted
        doAcquireShared(arg);
}
//Acquire shared locks, if CAS acquisition fails, queue the current node and block the current thread until locks are acquired
private void doAcquireShared(int arg) {
    //Insert the current node at the end of the queue
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (; ; ) {
            //Gets the precursor node of the current node, and if the precursor node is the head node, attempts to acquire the lock;
            //Check node state if acquisition fails; block node thread when node state is SIGNAL
            final Node p = node.predecessor();
            if (p == head) {
                //CAS Acquire Lock
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    //Success sets the current node as the head node and other node states as PROPAGAE
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            //Check if the current node should be blocked, or block until it is interrupted
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        //Cancel acquisition if lock acquisition fails
        if (failed)
            cancelAcquire(node);
    }
}
//Set current node as head node and wake up threads in shared mode
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; 
    //Set as Head Node
    setHead(node);
    //If propagate > 0 or header node is empty and header node state is < 0 
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        //Get the header node if its successor is in shared mode
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

5.3. Shared lock release related methods

//Release shared lock
public final boolean releaseShared(int arg) {
    //cas releases locks, doReleaseShared releases locks if it fails
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
private void doReleaseShared() {
    for (; ; ) {
        //When getting the header node, the header node is not empty and the state is SIGNAL, CAS sets the state to 0 and wakes the thread
        //Otherwise, set the header node state to PROPAGATE, then cycle through the header node state and try to wake up
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            } else if (ws == 0 &&
                    !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

6. Conditions

6.1. Implementation of condition

First, there is a Condition queue internally that stores all the threads waiting for this Condition condition.

await family method: let the thread currently holding the lock release the lock, wake up a thread waiting for the lock on the CLH queue, create a node node for the current thread, and insert it into the Condition queue (note that it is not inserted into the CLH queue)

signal family method: Instead of waking any threads here, wait nodes on the Condition queue are inserted into the CLH queue, so when the thread holding the lock finishes executing the release of the lock, one of the threads in the CLH queue is waked up, and then the thread is waked up.

6.2, await and signal processing

await process.png
signal Processing Process

6.3. await related methods

//Block the thread currently holding the lock, wait, and release the lock.If there is an interrupt request, an InterruptedException exception is thrown
public final void await() throws InterruptedException {
    //Throw an interrupt exception if the current thread has been interrupted
    if (Thread.interrupted())
        throw new InterruptedException();
   // Create a new Node node for the current thread and insert it into the Condition queue
    Node node = addConditionWaiter();
    //Release locks held by the current thread and wake up the header node in the synchronization queue
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //If the current node fills the synchronization queue;
    //Blocks the current thread, which joins the current node to the synchronization queue after it is awakened by a signal signal;
    //Waiting to acquire lock
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        //Check if interrupted and queued for locks
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // If the node node node is already in the synchronization queue, the synchronization lock is acquired, and the execution can continue only if the lock is acquired, otherwise the thread continues to block and wait
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    // Clear nodes in Condition queue whose state is not Node.CONDITION    
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    // Do you want to throw an exception or issue an interrupt request
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
//Create a new Node node for the current thread and insert it into the Condition queue
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If the wait queue tail node state is not CONDITION, the clean up operation is performed;
    // Clean up nodes in queue whose state is not CONDITION
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    //Create a node with CONDITION status for the current thread and insert the node at the end of the queue
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}
//Traverse through the waiting queue from beginning to end, removing nodes whose state is not CONDITION
private void unlinkCancelledWaiters() {
    //Record the next node to be processed
    Node t = firstWaiter;
    //Log the previous node with CONDITION status
    Node trail = null;
    while (t != null) {
        Node next = t.nextWaiter;
        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;
    }
}
//Release lock held by current thread and wake up synchronization queue a waiting thread
//If it fails, throw an exception and set the node's state to Node.CANCELLED
final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
        //Release locks held by the current thread
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}
//Determine whether a node is released in a synchronization queue
final boolean isOnSyncQueue(Node node) {
    // If the status of a node is Node.CONDITION, or if the node does not have a previous node prev,
    // Then return false, the node node is not in the synchronization queue
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    //If a node has the next node, it must be in the synchronization queue    
    if (node.next != null) // If has successor, it must be on queue
        return true;
    //Find node from Synchronization Queue
    return findNodeFromTail(node);
}

//Based on the current pattern, determine whether to throw an exception or break again, etc.
private void reportInterruptAfterWait(int interruptMode)
        throws InterruptedException {
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    else if (interruptMode == REINTERRUPT)
        selfInterrupt();
}

6.4. signal-related methods

//If the wait queue is not empty, insert the queue header node into the synchronization queue
public final void signal() {
    //Throw an exception if the current thread is not an exclusive lock
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    //Insert the header node in the waiting queue into the synchronization queue
    if (first != null)
        doSignal(first);
}
//Insert the header node waiting for the total queue into the synchronous queue
private void doSignal(Node first) {
    do {
        // The original Condition queue header node was cancelled, so the Condition queue header node was reassigned
        // If the new Condition queue header node is null, the Condition queue is empty
        // So also set the last Waiter at the end of the Condition queue to null
        if ((firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
            (first = firstWaiter) != null);
}
// Returning true indicates that the node node is inserted in the synchronization queue, returning false indicates that the node is not inserted in the synchronization queue
final boolean transferForSignal(Node node) {
    //If the node state cannot be modified from CONDITION to 0, the node is already in the synchronization queue and returns false directly
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    //Inserts the node node node into the synchronization queue, p is the original synchronization queue end node and the previous node of the node
    Node p = enq(node);
    int ws = p.waitStatus;
    // If the previous node is in a canceled state, or it cannot be set to a Node.SIGNAL state.
    // This means that node p will not wake up the next node thread.
    // So call the LockSupport.unpark(node.thread) method directly here to wake up the thread on which the node node is located
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}
//Wake up all nodes
public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first);
}
//Loop wakes up all waiting nodes
private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}

Posted by js_280 on Thu, 18 Jul 2019 17:19:16 -0700