Analysis of ReentrantLock source code for Java concurrency

Posted by mblack0508 on Sat, 22 Jan 2022 08:45:31 +0100

Analysis of ReentrantLock source code for Java concurrency

Condition

In the previous chapter, we learned about the use of condition. Let's take a look at the implementation of condition juc. Condition under juc is essentially an interface, which only defines the use mode of this interface, and the specific implementation is actually completed by subclasses. [obtain resources]

public interface Condition {
    void await() throws InterruptedException;
    void awaitUninterruptibly();
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    boolean awaitUntil(Date deadline) throws InterruptedException;
    void signal();
    void signalAll();
}

I believe there is no need for the author to introduce the three methods of await(), signal(), and signalAll(). Some people even know the functions of these methods when they see other methods. Here, the author briefly introduces:

Awaituninterruptible(): after releasing the lock and suspending the current thread, this method will ignore the interrupt until it wakes up after receiving the signal that the condition is true, and execute the code that the condition is true.
awaitNanos(long nanosTimeout): pass in a time in nanoseconds. If the returned value > 0, it means that the waiting thread obtains the signal that the condition is true within a given time, and the code after the condition is true can be executed; If it is less than or equal to 0, it means that the thread does not receive the signal that the condition is true within a given time and cannot execute the code that the condition is true.
await(long time, TimeUnit unit) is similar to awaitUntil(Date deadline), which also limits a timeout period. Within the timeout period, if a signal that the condition is true is received, it returns true to execute the code after the condition is true. Otherwise, the code after the condition is true cannot be executed.
Next, let's take a look at the specific implementation of the Condition interface under the juc package. In fact, two internal classes implement the Condition interface respectively under the juc package. The first is our old friend: abstractqueuedsynchronizer Conditionobject, the second is abstractqueuedlongsynchronizer ConditionObject, as like as two peas, the two internal classes are very similar, and even the same is true. Here is the AbstractQueuedSynchronizer. Implementation of conditionobject. [obtain resources]

Let's first look at the await () method to see how the lock is released in the blocked Thread in this method and how the lock is re robbed when the Thread is awakened. When the Thread enters await(), it will first create a Node that encapsulates the Thread object of the Thread itself at < 1 > and then release the lock held by the Thread at < 2 >. If multiple threads enter the await () method of the same condition object, each Thread will have a corresponding Node node, and these Node nodes will form a queue. After releasing the lock, the Thread will enter < 3 > and fall into blocking until it receives the signal that the condition is true or the signal that the Thread is interrupted, so the Thread will wake up from < 3 > and jump out of the loop. At < 4 >, we see our old friend acquirequeueueueued (final Node, long ARG). If a friend with good memory will know that this method can also block the Thread until the Thread grabs the lock. In this way, the await method does something similar to the wait() method.

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    //...
    public class ConditionObject implements Condition, java.io.Serializable {
 
        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();//<1>
            long savedState = fullyRelease(node);//<2>
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);//<3>
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)//<4>
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
    }
    //...
}
  

Of course, the above await() method is far more than what the author said. The knowledge just mentioned by the author is a general context, in which there are many details worth studying. Now please join the author to understand the principle of await() line by line and sentence by sentence.

Starting with await(), we can see that the ConditionObject class itself maintains two fields firstWaiter and lastWaiter, which can form a waiting queue for a CONDITION object. When a thread calls addConditionWaiter() method, it will first check whether the current thread is a thread with exclusive lock at < 1 >, if not, throw an exception, if yes, continue to execute, and then obtain the tail Node of the CONDITION object. If the tail Node is not empty and the state of the tail Node is not Node CONDITION. Here, it will be judged that the CONDITION Node is no longer a valid CONDITION Node, and unlikcancelledwaiters() at < 2 > will be called to remove the CONDITION Node from the queue, and then get the tail Node again. After that, a Node will be created for the calling thread, and the waiting status is CONDITION (- 2), indicating that this is a CONDITION Node. If the tail Node is empty, it means that there is no Node in the current queue, so execute the code at < 3 > to assign the current Node to the head Node (firstWaiter). If the tail Node is not empty, assign the current Node to the nextWaiter of the original tail Node, Then assign the current Node to the tail Node. Since this code can only be executed by the thread occupying the lock, this code is thread safe.

Next, let's take a look at how unlikcancelledwaiters() removes a non waiting node from the queue. In the conditional queue, as long as the state is not equal to CONDITION, it is a non conditional node. All nodes entering the conditional queue are conditional nodes at first, but they may change from conditional nodes to non conditional nodes for various reasons, so they should be removed from the queue. As for the reasons, I will elaborate later. Here, as long as we know that conditional nodes will change into non conditional nodes, Once this happens, you need to remove the non conditional node from the conditional queue. This is similar to and slightly different from the original AQS node. The original AQS believed that as long as the waiting state of the node is > 0, it is a failed node and should be removed from the queue. [obtain resources]

When you want to remove a non conditional node from the conditional queue, you will cycle through the node from the beginning, and next points to the next node of the current node, which is used to judge whether there are conditional nodes in the next round. If the traversed node is a conditional node, the code at < 5 > will be executed to assign the current node to trail, so trail always points to the last conditional node in the traversal process. If the current node is not a conditional node, the nextWaiter of the current node will be cleared for GC. At the same time, check whether the effective node trail closest to the current node is null. Null means that neither the head node nor the current node is a valid node, so assign the next node to the head node, otherwise the last conditional node trail will be nextWaiter points to the next node until next is null and jumps out of the loop. If neither the head node nor the tail node is a condition node, the trail will always be null, and the branch at < 4 > will be null until the end, and there are no nodes in the condition queue.

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    //...
    private transient volatile Node head;
    private transient volatile Node tail;
    //...
    static final class Node {
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;
        static final int CANCELLED =  1;//Node status is cancelled
        static final int SIGNAL    = -1;//The successor nodes of the current node are waiting to wake up
        static final int CONDITION = -2;//The node waiting condition is true
        volatile int waitStatus;//Waiting state
        volatile Thread thread;//The node points to the thread object
        /**
         * It is generally used to connect to the next Condition node, or use the special value node SHARED
         * Indicates that this is a shared node.
         */
        Node nextWaiter;
        //...
        Node(int waitStatus) {
            WAITSTATUS.set(this, waitStatus);
            THREAD.set(this, Thread.currentThread());
        }
        //...
    }
    public class ConditionObject implements Condition, java.io.Serializable {
        //...
        private transient Node firstWaiter;
        private transient Node lastWaiter;
        //...
        private Node addConditionWaiter() {
            if (!isHeldExclusively())//<1>
                throw new IllegalMonitorStateException();
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();//<2>
                t = lastWaiter;
            }
 
            Node node = new Node(Node.CONDITION);
 
            if (t == null)
                firstWaiter = node;//<3>
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }
        //...
        private void unlinkCancelledWaiters() {
            Node t = firstWaiter;
            Node trail = null;
            while (t != null) {
                Node next = t.nextWaiter;
                if (t.waitStatus != Node.CONDITION) {//<4>
                    t.nextWaiter = null;
                    if (trail == null)
                        firstWaiter = next;
                    else
                        trail.nextWaiter = next;
                    if (next == null)
                        lastWaiter = trail;
                }
                else
                    trail = t;//<5>
                t = next;
            }
        }
    }
    //...
}
  

After encapsulating the thread as a Node node and adding it to the condition queue, await() will also call fullyRelease(Node node) to release the lock. You can see that getState() is called to obtain the reference count of the lock, and then release(int arg) is called to clear the reference count of the lock. If this method is not called by the thread occupying the lock, release(int arg) will return false, An IllegalMonitorStateException will be thrown here. If the lock is released successfully, the original reference count savedState will be returned.

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    //...
    final int fullyRelease(Node node) {
        try {
            int savedState = getState();
            if (release(savedState))
                return savedState;
            throw new IllegalMonitorStateException();
        } catch (Throwable t) {
            node.waitStatus = Node.CANCELLED;
            throw t;
        }
    }
    //...
    public class ConditionObject implements Condition, java.io.Serializable {
        //...
        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
        //...
    }
    //...
}
    

After that, it will judge whether the current node is in the synchronization queue, that is, whether the node is in the queue of AQS header and tail. It must not be here. Because it can be determined that the waiting state of the current node is CONDITION at < 1 > of the following code, and the node has no precursor node, so the isOnSyncQueue(Node node) will return false,! If isOnSyncQueue(node) is true, a loop at < 2 > will be entered to block the current thread.

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    //...
    final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)//<1>
            return false;
        if (node.next != null) // If has successor, it must be on queue
            return true;
        /*
         * node.prev can be non-null, but not yet on queue because
         * the CAS to place it on queue can fail. So we have to
         * traverse from tail to make sure it actually made it.  It
         * will always be near the tail in calls to this method, and
         * unless the CAS failed (which is unlikely), it will be
         * there, so we hardly ever traverse much.
         */
        return findNodeFromTail(node);
    }
    //...
    public class ConditionObject implements Condition, java.io.Serializable {
        //...
        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {//<2>
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
        //...
    }
    //...
}
  

Since the current thread has been blocked, we must find a way to wake up the thread and let the thread continue to execute. We all know that there are two ways to wake up the thread blocked in await(), one is to call the signal() method, and the other is to interrupt the thread. Let's start with signal(). [obtain resources]

When we call signal(), we still first judge whether the calling thread now owns the lock. If it is not the thread that owns the lock, we call this method to throw an IllegalMonitorStateException. If the thread is the exclusive thread of the lock, we will continue to execute. After that, get the head node of the condition queue (firstWaiter). If the head node is not empty, pass the head node into the doSignal(Node first) method. [obtain resources]

Observing the doSignal(Node first) method, we know that the first incoming first will execute transferForSignal(Node node) once. The return result of this method determines whether the loop in the doSignal(Node first) method can continue. Let's take a look at the method of transferForSignal(Node node).

If the node we passed in transferForSignal(Node node) is a conditional node, that is, the waiting state is CONDITION, then the result at < 1 > must be true. At this time, the original conditional node becomes a non conditional node. In other words, this node should be removed from the CONDITION queue. Then, execute the code at < 2 > to enter the node into the synchronization queue, that is, the AQS queue. If the queue is successful, the predecessor node of the current node will be returned at < 3 >. Then judge whether the precursor node state of the node is invalid, or whether the waiting state of the precursor node can be changed to SIGNAL by CAS. If it is invalid or CAS modification fails, wake up the thread of the node and finally return true.

If this process is followed, the return of transferForSignal(first) is not true, (! transferForSignal(first) & & (first = firstWaiter)= Null) will not hold, exit the loop. In addition, we note that in the doSignal(Node first) method, the next node (first.nextWaiter) of the original head node (first) is assigned to the head node (firstWaiter) of the waiting object every time the loop is executed. Whether the return of transferForSignal(Node node) is true or false, the original head node will be removed, If the node we passed in the condition queue is no longer a condition node, and there are other nodes in the queue, (! transferForSignal(first) & & (first = firstWaiter)= Null) will hold until the top conditional node in the queue is encountered or there is no node in the queue. During the cycle, if the head node (firstWaiter) is found to be empty, it means that there are no redundant nodes in the queue, and the tail node (firstWaiter) will also be set. At the same time, the reference of the nextWaiter of the original first node (first) will be set to be empty every cycle to facilitate garbage collection [obtain resources].

So to sum up, the function of doSignal(Node first) is to start from the head node of the condition queue to judge whether the node is a condition node. If so, modify its waiting state, put it into the synchronization queue of AQS, and remove the node from the condition queue. If the head node is not a conditional node, the non conditional node will be removed while traversing until a conditional node is encountered or there is no node in the conditional queue. [obtain resources]

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    //...
    final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        //If the waiting state of the incoming node is not CONDITION, it means that the node is no longer a CONDITION node, and false is returned
        if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))//<1>
            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).
         */
        //If the incoming node is a conditional node, the status of the node here will be changed from CONDITION to 0, the original conditional node will be transformed into a non conditional node, and enter the synchronization queue of AQS
        Node p = enq(node);//<2>
        int ws = p.waitStatus;
        if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))//<4>
            LockSupport.unpark(node.thread);
        return true;
    }
    //...
    private Node enq(Node node) {
        for (;;) {
            Node oldTail = tail;
            if (oldTail != null) {
                node.setPrevRelaxed(oldTail);
                if (compareAndSetTail(oldTail, node)) {
                    oldTail.next = node;
                    return oldTail;//<3>
                }
            } else {
                initializeSyncQueue();
            }
        }
    }
    //...
    public class ConditionObject implements Condition, java.io.Serializable {
        //...
        public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
        //...
        private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
        //...
    }
    //...
}
  

After calling the signal() method to remove the Condition node from the condition queue and put it into the AQS synchronization queue, when the first thread releases the lock and wakes up the thread of the next node of the synchronization queue header node, the thread that originally called await() and fell into a block will be removed from the locksupport at loop < 1 > Park (this) wakes up, and then executes the checkInterruptWhileWaiting(node) method. If the result is not 0, it jumps out of the loop. If the result is 0, it determines whether the isOnSyncQueue(node) condition is true. If the result of the range of isOnSyncQueue(node) is true, it jumps out of the loop. Otherwise, it continues the loop. [obtain resources]

Let's take a look at the method checkInterruptWhileWaiting(node) that is executed first after the blocking thread is awakened. This method is also simple. It only judges whether the thread is interrupted. If not, it returns 0. Our thread is not awakened by interruption. The return result here must be 0, so it cannot enter the branch at < 2 > and jump out of the loop. After that, we judge whether the CONDITION of isOnSyncQueue(node) is true. Firstly, the waiting state of the node is not CONDITION. Secondly, after the current node enters the synchronization queue, there must be a precursor node, so the branch at < 3 > cannot enter. If a node has a successor node, it can be determined that this node must be a node in the synchronization queue. However, after our node enters the synchronization queue, we assume that no other thread requests a lock. Therefore, our node has no successor node, and there is no branch < 4 >. After that, we can only call findNodeFromTail(node) to find the current node from the tail node. We can be sure that we can find the current node by traversing from the tail of the synchronization queue, because the current node is the tail node. Therefore, the isOnSyncQueue(node) will eventually return true, indicating that the current node is already in the synchronization queue. Therefore, while (!isOnSyncQueue(node)) is determined to be false and jumps out of the loop. [obtain resources]

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    //...
    public class ConditionObject implements Condition, java.io.Serializable {
        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {//<1>
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)//<2>
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
        //...
        private int checkInterruptWhileWaiting(Node node) {
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }
    }
    //...
    final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)//<3>
            return false;
        if (node.next != null) // If has successor, it must be on queue <4>
            return true;
        /*
         * node.prev can be non-null, but not yet on queue because
         * the CAS to place it on queue can fail. So we have to
         * traverse from tail to make sure it actually made it.  It
         * will always be near the tail in calls to this method, and
         * unless the CAS failed (which is unlikely), it will be
         * there, so we hardly ever traverse much.
         */
        return findNodeFromTail(node);
    }
    //...
    private boolean findNodeFromTail(Node node) {
        // We check for node first, since it's likely to be at or near tail.
        // tail is known to be non-null, so we could re-order to "save"
        // one null check, but we leave it this way to help the VM.
        for (Node p = tail;;) {//<5>
            if (p == node)
                return true;
            if (p == null)
                return false;
            p = p.prev;
        }
    }
    //...
}
  

After knowing how to wake up a blocked thread through signal(), let's take a look at the process of interrupting a blocked thread. After the blocking thread is interrupted, from locksupport Park (this) is awakened to execute the logic in checkinterrupt while waiting (node). Here, judge thread The interrupted () result is true, so the return result of checkInterruptWhileWaiting(node) is to return throw according to transferaftercanceledwait (node)_ IE (- 1) or REINTERRUPT (1).

When the transferaftercanceledwait (node) method is called, if the waiting state of the node can be successfully modified by CAS at < 1 > from CONDITION node to 0, the current node will be put into the synchronization queue and return true (transferaftercanceledwait (node)? THROW_IE: REINTERRUPT)_ Ie is returned as a result, indicating that the status of the node is a conditional node from the interruption period to the node joining the queue. If the state of the node at < 1 > is no longer a CONDITION, it means that the state of the node has been changed. It is not certain that the waiting state of the node is changed because another thread executes signal() before the interrupt, or the current thread is interrupted first, and then another thread calls signal() to change the waiting state of the node. Once this happens, It cannot enter the branch at < 1 > and will continue to go down. At < 2 >, it will cycle to judge whether the current node enters the synchronization queue. If it is not in the synchronization queue, it will cycle until another thread puts the current node into the synchronization queue, and finally returns false (transferaftercanceledwait (node)? THROW_IE: REINTERRUPT) will return REINTERRUPT as a result. But whether it's returning to THROW_IE or REINTERRUPT, we can enter the branch < 3 > and jump out of the loop.

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    //...
    public class ConditionObject implements Condition, java.io.Serializable {
        //...
        private static final int REINTERRUPT =  1;
        private static final int THROW_IE    = -1;
        //...
        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)//<3>
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
        //...
        private int checkInterruptWhileWaiting(Node node) {
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }
    }
    //...
    final boolean transferAfterCancelledWait(Node node) {
        if (node.compareAndSetWaitStatus(Node.CONDITION, 0)) {//<1>
            enq(node);
            return true;
        }
        /*
         * If we lost out to a signal(), then we can't proceed
         * until it finishes its enq().  Cancelling during an
         * incomplete transfer is both rare and transient, so just
         * spin.
         */
        while (!isOnSyncQueue(node))//<2>
            Thread.yield();
        return false;
    }
    //...
}
  

We have seen how the blocking thread wakes up and leaves the loop after the blocking thread is interrupted. Let's see how the thread leaving the loop in these two ways will execute the subsequent code. First, the node corresponding to the thread has entered the synchronization team from the condition queue, so acquirequeueueueueueueued (node, savedstate) will be called at < 1 > to try to obtain the lock. If the lock contention fails, the current thread will be blocked until it is awakened to restart a new round of contention until the lock contention succeeds. Moreover, acquirequeueueueueued (node, savedstate) will return whether the current thread has been interrupted. If the thread is awakened with signal(), there is no interrupt mark. After successfully robbing the lock and exiting from acquirequeueueueueueueued (node, savedstate), the return result is false. The branch at < 1 > will not be entered here, so the interruptMode is 0. Then judge the node. If nextWaiter is not empty, clean up the conditional queue and remove the non conditional nodes in the queue. Since interruptMode is 0, the branch at < 3 > will not be entered here.

If the thread is interrupted, the result returned is true after successfully exiting the acquirequeueueueueueueueueued (node, savedstate) method. If it is judged that the interruptMode is not thread_ Ie means that the current thread cannot judge whether the interrupt is executed before signal() or after signal(), so mark the interrupt mode as REINTERRUPT, enter the branch at < 3 >, execute the reportInterruptAfterWait(int interruptMode) method, and set the interrupt flag of the current thread to true. If interruptMode is THROW_IE indicates that the waiting state of the node modified by CAS during the interrupt period is non conditional success, and no other thread modifies the node state. Therefore, the branch at < 3 > will throw an interrupt exception when it executes reportInterruptAfterWait(int interruptMode).

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    //...
    public class ConditionObject implements Condition, java.io.Serializable {
        //...
        private static final int REINTERRUPT =  1;
        private static final int THROW_IE    = -1;
        //...
        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)//<1>
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled <2>
                unlinkCancelledWaiters();
            if (interruptMode != 0)//<3>
                reportInterruptAfterWait(interruptMode);
        }
        //...
    }
    //...
    private void reportInterruptAfterWait(int interruptMode)
        throws InterruptedException {
        if (interruptMode == THROW_IE)
            throw new InterruptedException();
        else if (interruptMode == REINTERRUPT)
            selfInterrupt();
    }
    //...
}
  

Next, the author introduces the awaitNanos(long nanosTimeout) method. At < 1 >, the expiration time is calculated according to the current system time plus the external incoming waiting time nanosTimeout. Then use initialNanos to save the initial waiting time, because the cycle of this waiting time at < 3 > may be reduced, and the initial remaining time is required later. Then we call the addConditionWaiter() method to put the current thread into the condition queue as a Node node, then call fullyRelease(node) to release the lock occupied by the current thread, and then enter the cycle at < 3 >. In this cycle, the remaining waiting time nanosTimeout will continue to decrease. If it is less than or equal to 0, we will exit the cycle; If it is judged that the remaining waiting time is greater than 1000ns (SPIN_FOR_TIMEOUT_THRESHOLD) during the cycle, the timing blocking is selected. If the thread is interrupted during the blocking period, or the thread wakes up after reaching the expiration time, the subsequent checkinterrupt while waiting (Node) will be executed. This method will not be introduced here. This method was introduced earlier, It also explains how interrupts and wakes leave the loop.
After leaving the loop, the thread calls acquirequeueueueueueueueued (node, savedstate) to grab the lock again. After the lock grab is successful, judge whether to throw an exception, mark the thread interrupt state, or nothing happens according to the interrupt mode interruptMode.

If the thread is awakened or the thread interrupt state is re marked, the code after < 5 > will be executed. Here, the remaining time will be calculated according to the expiration time and the current time. Generally, the remaining time is less than the waiting time (initialNanos), unless overflow occurs. If the remaining time is less than the waiting time, the remaining time is returned; otherwise, long is returned in case of overflow MIN_ VALUE.

public class ConditionObject implements Condition, java.io.Serializable {
    //...
    static final long SPIN_FOR_TIMEOUT_THRESHOLD = 1000L;
    //...
    public final long awaitNanos(long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // We don't check for nanosTimeout <= 0L here, to allow
        // awaitNanos(0) as a way to "yield the lock".
        final long deadline = System.nanoTime() + nanosTimeout;//<1>
        long initialNanos = nanosTimeout;//<2>
        Node node = addConditionWaiter();
        int savedState = fullyRelease(node);
        int interruptMode = 0;
        while (!isOnSyncQueue(node)) {//<3>
            if (nanosTimeout <= 0L) {
                transferAfterCancelledWait(node);
                break;
            }
            if (nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD)
                LockSupport.parkNanos(this, nanosTimeout);
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
            nanosTimeout = deadline - System.nanoTime();//<4>
        }
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            interruptMode = REINTERRUPT;
        if (node.nextWaiter != null)
            unlinkCancelledWaiters();
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
        long remaining = deadline - System.nanoTime(); // avoid overflow<5>
        return (remaining <= initialNanos) ? remaining : Long.MIN_VALUE;
    }
    //...
}
  

Finally, the author introduces signalAll() and ends the chapter of ReentrantLock source code analysis. Of course, the implementation of Condition in AQS is not completely introduced here. There are still some methods left: awaituninterruptible (), await(long time, TimeUnit unit) and awaitUntil(Date deadline), but these methods are similar to awaitNanos(long nanosTimeout). I believe you can think about these methods independently.

Next, let's take a look at signalAll(). In fact, this method can be easily understood as long as it has the previous basic knowledge. The difference between signalAll() and signal() is that signal() calls the transferForSignal(first) method. As long as a conditional node in the conditional queue is transformed into a non conditional node and enters the synchronization queue, it will exit. Signalall () will call the transferForSignal(first) method to pass in all nodes in the queue and add them to the synchronization queue until the condition queue is empty.

public class ConditionObject implements Condition, java.io.Serializable {
    //...
    public final void signalAll() {
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        Node first = firstWaiter;
        if (first != null)
            doSignalAll(first);
    }
    //...
    private void doSignalAll(Node first) {
        lastWaiter = firstWaiter = null;
        do {
            Node next = first.nextWaiter;
            first.nextWaiter = null;
            transferForSignal(first);
            first = next;
        } while (first != null);
    }
    //...
}
  

So far, we have understood the use and implementation principle of ReentrantLock. I don't know if you have such feelings. This chapter is not so much about ReentrantLock as AQS. The internal classes of ReentrantLock only implement some interfaces required by AQS and have their own characteristics.

In fact, the author's real purpose is to take you to understand AQS. In my opinion, AQS can definitely be said to be the core of juc package, and there is no one. It is just that AQS is too complex and abstract and needs to be viewed in multiple dimensions. So I use ReentrantLock as a starting point here to learn ReentrantLock and AQS with you.

ReentrantLock will come to an end. In the following chapters, the author will look at AQS from more angles with you. See our next article.

Finally, I wish you success in your studies as soon as possible, get a satisfactory offer, get a quick promotion and raise, and reach the peak of your life.

If you can, please give me a three company to support me??? [obtain data]

Topics: Java Back-end