Simple analysis of ReentrantLock source code

Posted by RHolm on Mon, 21 Feb 2022 22:04:12 +0100

Simple analysis of ReentrantLock source code

Most of the content comes from reprint https://www.cnblogs.com/waterystone/p/4920797.html , I only wrote the state and Condition sections,

  • Author: Water rock

  • source: http://www.cnblogs.com/waterystone

  • Articles not reproduced in this blog belong to the author Water rock It is shared with the blog Park and is welcome to reprint, but this statement must be retained without the consent of the author, and a link to the original text must be given in an obvious position on the article page, otherwise the right to investigate legal responsibility is reserved.

It only briefly analyzes the implementation of non fair locks, excluding fair locks.

Only the main processes are simply analyzed, without interruptions and exceptions.

Node status

Let's talk about node here. Node node is the encapsulation of each thread waiting to obtain resources, which includes the thread itself to be synchronized and its waiting state, such as whether it is blocked, whether it is waiting to wake up, whether it has been CANCELLED, etc. The variable waitStatus indicates the waiting status of the current node. There are five values: CANCELLED, SIGNAL, CONDITION, promote and 0.

  • CANCELLED(1): indicates that the current node has canceled scheduling. When a timeout or is interrupted (in the case of responding to an interrupt), it will trigger to change to this state, and the node after entering this state will not change again.

  • SIGNAL(-1): indicates that the successor node is waiting for the current node to wake up. When the successor node joins the queue, the status of the predecessor node will be updated to SIGNAL.

  • CONDITION(-2): indicates that the node is waiting on the condition. When other threads call the signal() method of the condition, the node in the condition state will be transferred from the condition queue to the synchronization queue and wait to obtain the synchronization lock.

  • PROPAGATE(-3): in the sharing mode, the predecessor node will wake up not only its successor node, but also the successor node.

  • 0: the default status when new nodes join the queue.

Note that a negative value indicates that the node is in a valid waiting state, while a positive value indicates that the node has been cancelled. Therefore, many places in the source code use > 0 and < 0 to judge whether the node state is normal.

state

State is a volatile int variable of AbstractQueuedSynchronizer(AQS), which represents the resources of thread synchronization. state == 0 means that no thread competes for resources. The resource sharing mode of ReentrantLock is exclusive. When the state is 1, it means that the thread has obtained the lock, and other threads will block when applying for the lock. ReentrantLock is a reentrant lock. When a thread applies for a lock again after it has obtained the lock, the value of state will be increased. When a thread releases the lock, the value of state will be reduced. No other thread can apply for a lock until the value of state is 0.

lock

Call reentrantLock. in thread The lock() method obtains the lock. Sync is an abstract subclass of AQS. FairSync and NonfairSync both inherit the sync class and represent fair locks and unfair locks respectively. In the lock() code, judge whether the lock can be obtained directly through cas. If it is a re-entry lock or other threads compete for an unreleased lock, use the acquire(1) method.

public void lock() {
    sync.lock();
}
// Implementation of unfair lock
static final class NonfairSync extends Sync {
    //The lock() method of the unfair lock actually calls here
    final void lock() {
        //Set the status through cas. When state==0, it means that the resource is not locked. Modify and set the lock obtained by the current thread through cas
        if (compareAndSetState(0, 1))
            //The current thread is set to obtain the lock
            setExclusiveOwnerThread(Thread.currentThread());
        else
            //Reentry locks and other threads go here
            acquire(1);
    }
}
//AQS class
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

The acquire method is implemented in the AQS class. This method is the top-level entry for threads to obtain shared resources in exclusive mode. If the resource is obtained, the thread returns directly. Otherwise, it enters the synchronization queue until the resource is obtained, and the impact of interruption is ignored in the whole process. Of course, this is not only the semantics of lock(), but also lock(). After obtaining the resource, the thread can execute its critical area code. Here is the source code of acquire():

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

The function flow is as follows:

  1. tryAcquire() attempts to obtain resources directly. If it succeeds, it will return directly (this reflects the unfair lock. When each thread obtains the lock, it will try to preempt and plug it directly once, and there may be other threads waiting in the CLH queue);

  2. addWaiter() adds the thread to the tail of the synchronization queue and marks it as exclusive mode;

  3. Acquirequeueueueued() causes the thread to block to obtain resources in the synchronization queue and return until the resources are obtained. If it is interrupted during the whole waiting process, it returns true; otherwise, it returns false.

  4. If a thread is interrupted while waiting, it does not respond. Only after obtaining the resources can self interrupt () be performed to make up the interrupt.

The following shows the implementation of the tryAcquire method for unfair locks:

As the name suggests, the tryacquire method means trying to acquire a lock. TryAcquire is an abstract method of AQS, implements tryAcquire method in NonfairSync, and calls nonfairTryAcquire method in parent class Sync. In the nonfairtryacquire method, first use cas to check whether the state is 0. If it is 0, it means that no thread has obtained the lock. You can obtain the lock directly, just like the lock method. If the state is not 0, check whether the thread applying for lock and the thread monopolizing resources are the same thread. If yes, it is a re-entry lock. If not, it returns false, and the attempt to apply for lock fails.

//AQS class abstract method
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
//The unfair Sync class implements the tryAcquire method
static final class NonfairSync extends Sync {
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
//Sync class
abstract static class Sync extends AbstractQueuedSynchronizer {
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            //Acquire lock
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //Reentry lock: check whether the thread applying for lock and the thread monopolizing resources are the same thread
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            //Set status
            setState(nextc);
            return true;
        }
        //The attempt to apply for a lock failed
        return false;
    }
}

Next, look at the addWaiter method. This method is used to add the current thread to the tail of the synchronization queue and return the node where the current thread is located.

private Node addWaiter(Node mode) {
    //Construct nodes in a given pattern. There are two mode s: EXCLUSIVE and SHARED
    Node node = new Node(Thread.currentThread(), mode);
    //Try a quick way to put it directly at the end of the team.
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        //cas checks whether pred is the tail node. If so, insert node as the new tail node
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //If you fail in the previous step, you will join the team through enq.
    enq(node);
    return node;
}

enq method, which is used to add node to the end of the queue.

If you've seen atomicinteger GetAndIncrement () function source code, then I believe you can see the essence of this code at a glance. CAS spin volatile variable is a classic usage. I don't know much about it. Go to Baidu by myself.

private Node enq(final Node node) {
    //CAS "spin" until you successfully join the tail of the team
    for (;;) {
        Node t = tail;
        if (t == null) { // If the queue is empty, create an empty flag node as the head node and point the tail to it.
            if (compareAndSetHead(new Node()))
                tail = head;
        } else { //Normal process, put it at the end of the team
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t; //The previous node of node is returned
            }
        }
    }
}

After executing the addWaiter method, return the Node that encapsulates the current thread, and call the acquirequeueueueued method to make the Node enter the waiting state.

OK, through tryAcquire() and addWaiter(), the thread failed to obtain resources and has been placed at the end of the synchronization queue. Smart, you should be able to think of what the thread should do next: enter the waiting state to rest until other threads wake up after completely releasing resources, get the resources, and then you can do what you want to do. Yes, that's it! Is it a little similar to the hospital queue to get the number ~ ~ acquirequeueueueueueueueued() is to do this: queue up to get the number in the synchronous queue (there is nothing else to rest in the middle), and then return until you get the number. This function is very important. Let's go to the source code:

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;//Mark whether the resource is successfully obtained
    try {
        boolean interrupted = false;//Mark whether the waiting process has been interrupted
        //Another spin!
        for (;;) {
            final Node p = node.predecessor();//Get the precursor, the previous node, and the previous node cannot be null
            //If the precursor is head, that is, the node has become the second child, then it is qualified to try to obtain resources (the boss may wake himself up after releasing resources, or it may be interrupt ed of course).
            if (p == head && tryAcquire(arg)) {
                setHead(node);//After getting the resource, point the head to the node. Therefore, the benchmark node referred to in head is the node that currently obtains the resource or null.
                p.next = null; // Node in setHead Prev has been set to null, and head Next is set to null to facilitate GC to recycle the previous head node. This means that the nodes that have taken the resources before are out of the team!
                failed = false; // Successfully obtained resources
                return interrupted;//Whether the return waiting process has been interrupted
            }
​
            //If you can rest, you can enter the waiting state through park() until you are unpark(). If you are interrupted without interruption, you will wake up from Park () and find that you can't get the resources, so you continue to enter Park () and wait.
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;//If the waiting process is interrupted even once, mark interrupted as true
        }
    } finally {
        if (failed) // If the resource is not obtained successfully during the waiting process (such as timeout, or it is interrupted when it can be interrupted), cancel the waiting of the node in the queue.
            cancelAcquire(node);
    }
}

Now that we're here, let's not rush to summarize the function flow of acquirequeueueueued(). Let's take a look at what shouldParkAfterFailedAcquire() and parkAndCheckInterrupt() do.

Take a look at the shouldParkAfterFailedAcquire method first: in the whole process, if the status of the precursor node is not SIGNAL, you can't rest at ease. You need to find a rest point at ease. At the same time, you can try again to see if it's your turn to take the number.

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;//Get the wait status of the precursor
    if (ws == Node.SIGNAL)
        //If you have told the driver to inform yourself after taking the number, you can rest at ease
        return true;
    if (ws > 0) {
        /*
         * If the precursor gives up, keep looking until you find the latest normal waiting state and stand behind it.
         * Note: those abandoned nodes are "plugged" in front of them by themselves, which is equivalent to forming a reference free chain. Later, they will be driven away by the security uncle (GC recycling)!
         * CANCELLED(1): Indicates that the current node has canceled scheduling. When a timeout or is interrupted (in the case of responding to an interrupt), it will trigger to change to this state, and the node after entering this state will not change again.
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
         //If the precursor is normal, set the status of the precursor to SIGNAL and tell it to notify itself after taking the number. It may fail. They may have just released it!
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

View the parkAndCheckInterrupt method

If the thread finds a safe rest point, it can rest at ease. This method is to let the thread rest and really enter the waiting state.

park() will put the current thread into the waiting state. In this state, there are two ways to wake up the thread:

  1. unpark()

  2. Interrupted ()

Pay attention to park(this): ReentrantLock Lock(), this is the ReentrantLock object, Condition When await(), this is the Condition object.

(again, if you are not familiar with thread state transition, you can refer to Thread detail ). It should be noted that thread Interrupted() clears the interrupt flag bit of the current thread.

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);//Call park() to put the thread into the waiting state
    return Thread.interrupted();//If you are awakened, check to see if you are interrupted.
}

Summary

OK, after watching shouldParkAfterFailedAcquire() and parkAndCheckInterrupt(), let's go back to acquirequeueueueued() and summarize the specific process of this function:

  1. After the node enters the tail of the team, check the status and find a safe rest point;

  2. Call park() to enter the waiting state and wait for unpark() or interrupt() to wake up;

  3. After being awakened, see if you are qualified to get the number. If you get it, the head points to the current node and returns whether the whole process from joining the team to getting the number has been interrupted; If you don't get it, continue with process 1.

After acquirequeueueueueued() analysis, let's go back to acquire()! Paste its source code:

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

Let's summarize its process:

  1. Call tryAcquire() of the user-defined synchronizer to try to obtain resources directly. If it is successful, it will return directly;

  2. If it fails, addWaiter() adds the thread to the tail of the synchronization queue and marks it as exclusive mode;

  3. Acquirequeueueueued() causes the thread to rest in the synchronization queue. When it has a chance (it's its turn, it will be unpark()) it will try to obtain resources. Return after obtaining the resource. If it is interrupted during the whole waiting process, it returns true; otherwise, it returns false.

  4. If a thread is interrupted while waiting, it does not respond. Only after obtaining the resources can self interrupt () be performed to make up the interrupt.

Since this function is the top priority, I will summarize it with the flow chart:

unlock

Call reentrantLock. in thread The unlock () method releases the lock. Which calls sync.release(1). The release method is implemented in the AQS class.

public void unlock() {
    sync.release(1);
}
//AQS
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;//Find header node
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);//Wake up the next thread in the synchronization queue
        return true;
    }
    return false;
}

The logic is not complicated. It calls tryrelease () to release resources. It should be noted that it determines whether the thread has released resources according to the return value of tryRelease()! Therefore, the user-defined synchronizer should be clear about this when designing tryRelease()!!

Next, look at the tryRelease method. This method attempts to free a specified amount of resources. The following is the source code of tryRelease():

//AQS
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}
​
//Sync
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) { //Release the lock completely
        free = true;
        setExclusiveOwnerThread(null);
    }
    //If the state is not equal to 0, it indicates that the re-entry lock is not fully released
    setState(c);
    return free;
}

Like tryAcquire(), this method needs a custom synchronizer in exclusive mode to implement. Normally, tryRelease() will succeed, because this is an exclusive mode. If the thread releases resources, it must have obtained the exclusive resources. Just reduce the corresponding amount of resources (state-=arg), and there is no need to consider thread safety. But pay attention to its return value. As mentioned above, release() determines whether the thread has completed releasing resources according to the return value of tryRelease()! Therefore, when implementing the self-defined synchronizer, if the resources have been completely released (state=0), it should return true, otherwise it will return false.

Next, look at the unparkwinner method. This method is used to wake up the next thread in the synchronization queue. Here is the source code:

//AQS
private void unparkSuccessor(Node node) {
    //Here, node is generally the node where the current thread is located.
    int ws = node.waitStatus;
    if (ws < 0)//Set the node state of the current thread to zero, allowing failure.
        compareAndSetWaitStatus(node, ws, 0);
​
    Node s = node.next;//Find the next node to wake up s
    if (s == null || s.waitStatus > 0) {//If empty or cancelled
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev) // Look back and forward.
            if (t.waitStatus <= 0)//It can be seen from here that nodes with < = 0 are still valid nodes.
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);//awaken
}

This function is not complicated. In a word: use unpark() to wake up the first thread in the synchronization queue that hasn't given up. Here we also use s. At this time, it will be connected with acquirequeueueueued(). After s is awakened, it will enter the judgment of if (p==head & & tryacquire (ARG)) (it doesn't matter even if p!=head. It will enter shouldParkAfterFailedAcquire() again to find a safe point. Here, since s is the first thread in the synchronization queue that hasn't given up, through the adjustment of shouldParkAfterFailedAcquire(), s will inevitably run to the next node of head, and the next spin p==head will be established). Then s sets itself as the head benchmark node, indicating that it has obtained resources, and acquire() returns.

Summary

release() is the top-level entry for threads to release shared resources in exclusive mode. It will release the specified amount of resources. If it is completely released (i.e. state=0), it will wake up other threads in the synchronization queue to obtain resources.

When the thread is blocked, through locksupport Park() makes the thread enter the waiting state through locksupport Unpark() wakes up the waiting thread.

Condition

await

First, let's take a look at the source code of await. We mainly pay attention to several methods: addConditionWaiter, fullyRelease, isOnSyncQueue and unlinkcanceledwaiters. Thread interrupts are not considered.

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter(); //Adds the current thread to the condition queue
    int savedState = fullyRelease(node); //Release the lock. Re entering the lock will release the resource at one time
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) { //Determine whether it is in the synchronization queue
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //Acquirequeueueued will try to acquire the lock after waking up, which will not be repeated
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters(); //Set waitstatus in the condition queue= Node. Node cleanup of condition
    if (interruptMode != 0) //Interruption will not be considered for the time being
       reportInterruptAfterWait(interruptMode);
}

addConditionWaiter

addConditionWaiter adds the current thread to the CONDITION queue. First, judge whether there are other nodes on the CONDITION queue. If so, clear those waitstatus on the CONDITION queue= Node. The nodes of CONDITION (because these nodes may have been added to the synchronization queue or cancelled). Next, create a new node with the current thread and the waiting state is CONDITION. Then add this node to the end of the CONDITION queue. If there is no node in the current CONDITION queue, the new node is both the head node and the tail node.

private Node addConditionWaiter() {
    Node t = lastWaiter;
    //If the waiting state of the node on the CONDITION queue is no longer a CONDITION, it will be cleared from the CONDITION queue
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter; //The tail node may be updated and needs to be reset
    }
    //Create a new node with the current thread and the waiting status is CONDITION, and then add this node to the end of the CONDITION queue
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

unlinkCancelledWaiters

If the node waiting state on the CONDITION queue is no longer a CONDITION, it will be cleared from the CONDITION queue. The code is simple, please analyze it yourself.

private void unlinkCancelledWaiters() {
    Node t = firstWaiter;
    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;
    }
}

fullyRelease

Release resources, not repeated

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        // The reentry lock may hold multiple resources, which can be released at one time and return the number of released resources
        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) {
    //Attempt to release lock (resource)
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h); //Wake up the successor node on the synchronization queue
        return true;
    }
    return false;
}

isOnSyncQueue

This method determines whether the current node is on the condition queue. In the await method, whether to exit the loop will be determined according to whether the node is in the synchronization queue. As for when to put the node into the synchronization queue, we will talk about it when we analyze the signalAll method below.

final boolean isOnSyncQueue(Node node) {
    //If the waiting state is CONDITION or the leading node on the synchronization queue is null
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    if (node.next != null) // If the post node on the synchronization queue is not null
        return true;
    return findNodeFromTail(node);
}
//If the node is on the synchronization queue, it returns true by searching backward from the tail. Called only when required by isOnSyncQueue.
private boolean findNodeFromTail(Node node) {
    Node t = tail;
    for (;;) {
        if (t == node)
            return true;
        if (t == null)
            return false;
        t = t.prev;
    }
}

signalAll

First on the source code. isHeldExclusively is simple, mainly because you need to know what doSignalAll does.

public final void signalAll() {
    //Determine whether the current thread holds a lock
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first);
}
​
protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}
//rewrite
protected final boolean isHeldExclusively() {
    // While we must in general read state before owner,
    // we don't need to do so to check if current thread is owner
    return getExclusiveOwnerThread() == Thread.currentThread();
}

doSignalAll

The source code comment of doSignalAll is remove and transfers all nodes Delete and transfer all nodes. Delete all nodes on the condition queue and add them to the synchronization queue.

private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first); //Add to sync queue
        first = next;
    } while (first != null);
}

transferForSignal

First, set the waiting state of the node from CONDITION to 0. If it fails, it will return (CAS. If the expected waiting state is not CONDITION, it will fail). After that, we call the enq method to add the nodes into the synchronization queue and return the nodes in the synchronous queue's pre node. Judge the waiting state of the front-end node. If it is in the canceled state, it will wake up directly without waking up through the synchronization queue. If it is not in the canceled state, it will try to set the waiting state of the front-end node to SIGNAL through CAS. If it fails, it will wake up directly. (direct wake-up should be an accident. I'm a vegetable chicken and can't understand the meaning of direct wake-up, so I copied the comments when I copied the code). signalAll is to wake up the nodes on the conditional queue at one time, that is, transfer the nodes of multiple conditional queues to the synchronization queue. When joining the tail of the synchronization queue, as long as the front node has not CANCELLED scheduling, the waiting state of the front node will be set to SIGNAL to wake itself up in the future.

/**
 * Transfers a node from a condition queue onto sync queue.
 * Returns true if successful.
 * @param node the node
 * @return true if successfully transferred (else the node was
 * cancelled before signal)
 */
final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
​
    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

Review await and summarize

In the await method, whether to exit the loop is selected by judging whether the node is in the synchronization queue. After calling signalAll, the nodes of the condition queue will be added to the synchronization queue and wait for the synchronization queue scheduling to wake up. When waking up, the node has become the head node of the synchronization queue and is naturally in the synchronization queue, so as to jump out of the loop.

while (!isOnSyncQueue(node)) {
    LockSupport.park(this); //Keep waiting
    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
        break;
}

Briefly summarize the main process of Condition:

  1. Call await

  2. Encapsulate the current thread as a node and add it to the condition queue

  3. Release lock

    Note: when releasing the lock, the unparksuccess method will be called to wake up the second node of the synchronization queue. After the lock is obtained, this node will become the head node of the synchronization queue

  4. Enter the waiting state

  5. Call signalAll

  6. Transfer the node of the condition queue to the synchronization queue

  7. Waiting for synchronization queue scheduling wake-up

Topics: Java Back-end