Java concurrent series: AbstractQueuedSynchronizer source code analysis (condition Queue ),
Through the analysis in the previous three articles, we have gained an in-depth understanding of the internal structure and design concepts of AbstractQueuedSynchronizer, and learned that AbstractQueuedSynchronizer maintains a synchronization status and two queuing zones, the two queues are divided into synchronization queues and condition queues. We still use the public restroom as a metaphor. The synchronization queue is the main queuing area. If the public restroom is not open, all the persons who want to enter the restroom must queue up here. The condition queue is mainly set for the condition wait. We imagine that if a person finally gets the lock through the queue and enters the toilet, but before the convenience, he finds that he has no toilet paper, despite the helplessness of this situation, it must accept this fact. At this time, it had to prepare the hand paper first (entering the condition queue for waiting ), of course, you have to release the lock before going out so that other people can come in. After preparing the hand paper (the conditions are met), it has to return to the synchronization queue to queue again. Of course, not all people entering the room do not bring their hands or paper, but there may be other reasons that the operation must be interrupted before queuing in the condition queue. Therefore, there may be multiple condition queues, set different conditional queues based on different waiting conditions. The Condition queue is a one-way linked list. The Condition interface defines all the operations in the Condition queue. The Condition object class in the Condition actqueuedsynchronizer implements the Condition interface. Let's look at the operations defined by the Condition interface.
Public interface Condition {// The Condition for responding to thread interruption waits for void await () throws InterruptedException; // The Condition for not responding to thread interruption waits for void awaitUninterruptibly (); // set the relative time condition wait (no spin) long awaitNanos (long nanosTimeout) throws InterruptedException; // set the relative time condition wait (spin) boolean await (long time, timeUnit) throws InterruptedException; // set the absolute time condition to wait for boolean awaitUntil (Date deadline) throws InterruptedException; // wake up the header node void signal () in the condition queue (); // wake up all nodes in the condition queue void signalAll ();}
Although the Condition interface defines so many methods, there are two types in total. The methods starting with await are the methods for threads to wait in the Condition queue, the method that starts with signal is to "wake up" the threads in the condition queue. It should be noted that calling the signal method may wake up the thread and may not wake up the thread. It depends on the situation when it will wake up the thread, however, calling the signal method will definitely move the thread from the condition queue to the end of the synchronization queue. Here, for the sake of convenience, we will not tangle so much for the moment. The signal method is called as the operation to wake up the condition queue thread. Note that the await method can be divided into five types: Response thread interrupt wait, no response thread interrupt wait, set relative time not to spin wait, and set relative time spin wait, set the absolute time wait. There are only two signal methods, namely, to wake up only the first node of the conditional queue and to wake up all nodes of the conditional queue. Methods of the same class are basically the same. Due to space limitations, it is impossible and we do not need to carefully discuss all these methods, you only need to understand a representative method and then look at other methods to bypass the class. So in this article, I will only elaborate on the await method and signal method. Other methods will not be detailed, but will post the source code for your reference.
1. Conditional waiting in response to thread interruption
// Response to Thread interruption conditions waiting for public final void await () throws InterruptedException {// if the Thread is interrupted, an exception is thrown if (Thread. interrupted () {throw new InterruptedException ();} // Add the current thread to the end of the condition queue Node node = addConditionWaiter (); // completely release the lock int savedState = fullyRelease (node); int interruptMode = 0 before waiting for the entry condition; // The thread waits for the while (! IsOnSyncQueue (node) {// threads waiting for conditions are suspended here, And the threads are awakened in the following situations: // 1. the frontend node of the synchronization queue has been canceled // 2. set the status of the frontend node of the synchronization queue to SIGNAL failed // 3. wake up the current node LockSupport after the slave node releases the lock. park (this); // check whether the thread is interrupted immediately after it wakes up. If yes, it indicates that the node cancels the conditional wait, in this case, you need to remove the node from the condition queue if (interruptMode = checkInterruptWhileWaiting (node ))! = 0) {break;} // After the thread wakes up, it will obtain the lock if (acquireQueued (node, savedState) & interruptMode! = THROW_IE) {interruptMode = REINTERRUPT;} // this step is mainly used to prevent the thread from being interrupted before the signal and thus failing to disconnect from the condition queue if (node. nextWaiter! = Null) {unlinkCancelledWaiters () ;}// response interrupt handling Based on interrupt mode if (interruptMode! = 0) {reportInterruptAfterWait (interruptMode );}}
When the thread calls the await method, it first wraps the current thread into a node and puts it at the end of the condition queue. In the addConditionWaiter method, if the end node of the condition queue is canceled, the unlinkCancelledWaiters method is called to clear all the canceled nodes of the condition queue. This step is the preparation for inserting the node. After the final node is in the CONDITION state, a new node will be created to wrap the current thread and put it at the end of the CONDITION queue. Note that this process only adds the node to the end of the synchronization queue without suspending the thread.
Step 2: Release the lock completely
// Completely release the lock final int fullyRelease (Node node) {boolean failed = true; try {// get the current synchronization status int savedState = getState (); // use the current synchronization status to release the lock if (release (savedState) {failed = false; // if the lock is successfully released, return the current synchronization status return savedState ;} else {// If the lock fails to be released, the runtime exception throw new IllegalMonitorStateException ();}} finally {// if the lock is not successfully released, set the node to the canceled state if (failed) {node. waitStatus = Node. CANCELLED ;}}}
Wrap the current thread as a node and add it to the end of the condition queue. Then call the fullyRelease method to release the lock. Note: The method named fullyRelease completely releases the lock in this step. Because the lock can be reentrant, you must release the lock before waiting for conditions, otherwise, the lock cannot be obtained. If the lock fails to be released, a runtime exception will be thrown. If the lock is released successfully, the previous synchronization status will be returned.
Step 3: Perform conditional waiting
// The thread waits for the while (! IsOnSyncQueue (node) {// threads waiting for conditions are suspended here, And the threads are awakened in the following situations: // 1. the frontend node of the synchronization queue has been canceled // 2. set the status of the frontend node of the synchronization queue to SIGNAL failed // 3. wake up the current node LockSupport after the slave node releases the lock. park (this); // check whether the thread is interrupted immediately after it wakes up. If yes, it indicates that the node cancels the conditional wait, in this case, you need to remove the node from the condition queue if (interruptMode = checkInterruptWhileWaiting (node ))! = 0) {break ;}}// check the thread interruption when the condition is waiting. private int checkInterruptWhileWaiting (Node node) {// before the signal operation, interrupt the request: THROW_IE // The interrupt request is not received after the signal operation: REINTERRUPT // during which no interrupt request is received: 0 return Thread. interrupted ()? (TransferAfterCancelledWait (node )? THROW_IE: REINTERRUPT): 0;} // transfers the Node that canceled the conditional wait from the conditional queue to the final boolean transferAfterCancelledWait (node Node) in the synchronization queue) {// if the CAS operation is successful in this step, it indicates that the interruption occurred before the signal method if (compareAndSetWaitStatus (node, Node. CONDITION, 0) {// after the status is successfully modified, the node will be placed at the end of the synchronization queue enq (node); return true ;}// here the CAS operation failed, it indicates that the interrupt occurs after the signal method while (! IsOnSyncQueue (node) {// If the sinal method has not transferred the node to the synchronization queue, wait for Thread. yield () ;}return false ;}
After the above two operations are completed, the while loop will enter. You can see that the while LOOP first calls LockSupport. park (this) to suspend the thread, so the thread will be blocked. After the signal method is called, only the node is transferred from the condition queue to the synchronization queue. It depends on whether the thread will be awakened. If the frontend node in the synchronization queue is canceled or the status of the frontend node is updated to SIGNAL, the thread will be immediately awakened in both cases, otherwise, when the signal method ends, it will not wake up the thread in the synchronization queue, but wait until its successor node to wake up. Of course, in addition to calling the signal method to wake up the thread congestion, the thread can also respond to the interruption. If the thread receives the interrupt request here, it will continue to execute. We can see that after the thread wakes up, it will immediately check whether the thread is awakened due to interruption or through the signal method. If the thread is awakened due to interruption, it will also transfer this node to the synchronization queue, it is implemented by calling the transferAfterCancelledWait method. After this step is completed, the system returns the interruption and jumps out of the while loop.
Step 4: perform operations after the node is removed from the condition queue
// When the thread wakes up, it will obtain the lock if (acquireQueued (node, savedState) & interruptMode! = THROW_IE) {interruptMode = REINTERRUPT;} // this step is mainly used to prevent the thread from being interrupted before the signal and thus failing to disconnect from the condition queue if (node. nextWaiter! = Null) {unlinkCancelledWaiters () ;}// response interrupt handling Based on interrupt mode if (interruptMode! = 0) {reportInterruptAfterWait (interruptMode);} // handle private void reportInterruptAfterWait (int interruptMode) based on the interrupt status after the end condition waits) throws InterruptedException {// if the interrupt mode is THROW_IE, an exception is thrown if (interruptMode = THROW_IE) {throw new InterruptedException (); // if the interrupt mode is REINTERRUPT, stop it.} else if (interruptMode = REINTERRUPT) {selfInterrupt ();}}
When the thread terminates the while LOOP, that is, the conditional wait, it will return to the synchronization queue. Whether it is because the signal method is called back or because of thread interruption, the node will eventually be in the synchronization queue. In this case, the acquireQueued method is called to obtain the lock in the synchronization queue. This method is described in detail in the exclusive mode. That is to say, after a node comes out of the condition queue, it is the one that uses the exclusive mode to obtain the lock. After the node gets the lock again, the reportInterruptAfterWait method will be called to respond accordingly based on the interruptions during this period. If the interrupt occurs before the signal method, interruptMode is THROW_IE, and an exception is thrown after the lock is obtained again. If the interrupt occurs after the signal method, interruptMode is REINTERRUPT, after the lock is obtained again, It is interrupted again.
2. Do not respond to conditions for thread interruption
// Wait for public final void awaitUninterruptibly () without responding to thread interruption conditions {// Add the current thread to the end of the condition queue Node node = addConditionWaiter (); // completely release the lock and return the current synchronization status int savedState = fullyRelease (node); boolean interrupted = false; // The node waits for the while loop for the condition while (! IsOnSyncQueue (node) {// All threads in the condition queue are suspended LockSupport. park (this); // The Thread does not immediately respond to if (Thread. interrupted () {interrupted = true ;}} if (acquireQueued (node, savedState) | interrupted) {// response to all interrupt requests here, if either of the following conditions is met, the user will be suspended // 1. the thread receives the interrupt request while waiting for a condition // 2. the thread receives the interrupt request selfInterrupt () ;}} in the acquireQueued method ();}}
3. Set the conditional wait for relative time (no spin)
// Set the scheduled condition wait (relative time). Do not run the spin wait public final long awaitNanos (long nanosTimeout) throws InterruptedException {// if the Thread is interrupted, an exception is thrown if (Thread. interrupted () {throw new InterruptedException ();} // Add the current thread to the end of the condition queue Node node = addConditionWaiter (); // completely release the lock int savedState = fullyRelease (node); long lastTime = System before waiting for the entry condition. nanoTime (); int interruptMode = 0; while (! IsOnSyncQueue (node) {// determine if the timeout time is used up if (nanosTimeout <= 0L) {// if the timeout time has exceeded, cancel the condition wait operation transferAfterCancelledWait (node ); break;} // suspends the current thread for a period of time. During this period, the thread may be awakened or LockSupport may be automatically woken up. parkNanos (this, nanosTimeout); // check the interrupt information if (interruptMode = checkInterruptWhileWaiting (node) when the thread wakes up ))! = 0) {break;} long now = System. nanoTime (); // The timeout time minus the Time of the condition wait. nanosTimeout-= now-lastTime; lastTime = now ;} // when the thread wakes up, it will obtain the lock if (acquireQueued (node, savedState) & interruptMode! = THROW_IE) {interruptMode = REINTERRUPT;} // Since the transferAfterCancelledWait method does not leave nextWaiter empty, we need to clear it again here if (node. nextWaiter! = Null) {unlinkCancelledWaiters () ;}// response interrupt handling Based on interrupt mode if (interruptMode! = 0) {reportInterruptAfterWait (interruptMode);} // return the remaining time return nanosTimeout-(System. nanoTime ()-lastTime );}
4. Set the condition wait for relative time (spin)
// Set the scheduled condition wait (relative time) for spin wait public final boolean await (long time, TimeUnit unit) throws InterruptedException {if (unit = null) {throw new NullPointerException ();} // obtain the number of milliseconds of the timeout value long nanosTimeout = unit. toNanos (time); // if the Thread is interrupted, an exception is thrown if (Thread. interrupted () {throw new InterruptedException ();} // Add the current thread to the end of the condition queue Node = addConditionWaiter (); // completely release the lock int savedState = fullyRelease (node) before entering the condition wait );/ /Obtain the current time in milliseconds long lastTime = System. nanoTime (); boolean timedout = false; int interruptMode = 0; while (! IsOnSyncQueue (node) {// if timeout occurs, you need to cancel the conditional wait operation if (nanosTimeout <= 0L) {timedout = transferAfterCancelledWait (node); break ;} // if the timeout value is greater than the spin time, the thread will be suspended for a period of time. if (nanosTimeout> = spinForTimeoutThreshold) {LockSupport. parkNanos (this, nanosTimeout);} // check the interrupt information if (interruptMode = checkInterruptWhileWaiting (node) after the thread wakes up ))! = 0) {break;} long now = System. nanoTime (); // The timeout time minus the Time of the condition wait. nanosTimeout-= now-lastTime; lastTime = now ;} // when the thread wakes up, it will obtain the lock if (acquireQueued (node, savedState) & interruptMode! = THROW_IE) {interruptMode = REINTERRUPT;} // Since the transferAfterCancelledWait method does not leave nextWaiter empty, we need to clear it again here if (node. nextWaiter! = Null) {unlinkCancelledWaiters () ;}// response interrupt handling Based on interrupt mode if (interruptMode! = 0) {reportInterruptAfterWait (interruptMode);} // returns the timeout flag: return! Timedout ;}
5. Set the absolute time condition wait
// Set the scheduled condition wait (absolute time) public final boolean awaitUntil (Date deadline) throws InterruptedException {if (deadline = null) {throw new NullPointerException ();} // obtain the absolute time in milliseconds long abstime = deadline. getTime (); // if the Thread is interrupted, an exception is thrown if (Thread. interrupted () {throw new InterruptedException ();} // Add the current thread to the end of the condition queue Node node = addConditionWaiter (); // completely release the lock int savedState = fullyRelease (node); boolean timedou before entering the condition wait T = false; int interruptMode = 0; while (! IsOnSyncQueue (node) {// if timeout occurs, you need to cancel the condition and wait for the operation if (System. currentTimeMillis ()> abstime) {timedout = transferAfterCancelledWait (node); break;} // suspends the thread for a period of time. During this period, the thread may be awakened, or you may wake up LockSupport by yourself. parkUntil (this, abstime); // check the interrupt information if (interruptMode = checkInterruptWhileWaiting (node) after the thread wakes up ))! = 0) {break;} // After the thread wakes up, it will obtain the lock if (acquireQueued (node, savedState) & interruptMode! = THROW_IE) {interruptMode = REINTERRUPT;} // Since the transferAfterCancelledWait method does not leave nextWaiter empty, we need to clear it again here if (node. nextWaiter! = Null) {unlinkCancelledWaiters () ;}// response interrupt handling Based on interrupt mode if (interruptMode! = 0) {reportInterruptAfterWait (interruptMode);} // returns the timeout flag: return! Timedout ;}
6. Wake up the header node in the condition queue
// Wake up the next node in the condition queue public final void signal () {// determine whether the current thread holds the lock if (! IsHeldExclusively () {throw new IllegalMonitorStateException ();} Node first = firstWaiter; // if there is a queue in the condition queue if (first! = Null) {// wake up the header Node doSignal (first) ;}// wake up the header Node private void doSignal (Node first) in the condition queue) {do {// 1. move the firstWaiter reference one if (firstWaiter = first. nextWaiter) = null) {lastWaiter = null;} // 2. set the reference of the successor node of the header node to null first. nextWaiter = null; // 3. after the first node is transferred to the synchronization queue, the thread may be awakened after the transfer is completed. // 4. if the transferForSignal operation fails, wake up the next node} while (! TransferForSignal (first) & (first = firstWaiter )! = Null);} // transfers the specified Node from the CONDITION queue to the final boolean transferForSignal (node Node) in the synchronization queue {// sets the wait state from CONDITION to 0 if (! CompareAndSetWaitStatus (node, Node. CONDITION, 0) {// If the Update Status operation fails, false is directly returned. // The transferAfterCancelledWait method changes the status first, causing the CAS operation to fail and return false ;} // Add the Node to the end of the synchronization queue node p = enq (Node); int ws = p. waitStatus; if (ws> 0 |! CompareAndSetWaitStatus (p, ws, Node. SIGNAL) {// The current thread will be awakened when the following occurs // 1. the frontend node is canceled. // 2. the status of the frontend node is changed to SIGNAL operation failure LockSupport. unpark (node. thread);} return true ;}
The final core of the signal method is to call the transferForSignal method. In the transferForSignal method, the CAS operation is used to set the node status from CONDITION to 0, then, call the enq method to add the node to the end of the synchronization queue. We can see the next if judgment Statement, which is mainly used to determine when the thread will be awakened. In either case, the thread will be immediately awakened, one is when the status of the frontend node is canceled, and the other is when the status of the frontend node fails to be updated. In both cases, the thread will be awakened immediately. Otherwise, the node will only be transferred from the condition queue to the synchronization queue, instead of immediately awakening the thread in the node. The signalAll method is similar, except that it cyclically traverses all nodes in the condition queue and transfers them to the synchronization queue. The transfer node method also calls the transferForSignal method.
7. Wake up all nodes in the condition queue
// Wake up all nodes after the condition queue public final void signalAll () {// judge whether the current thread holds the lock if (! IsHeldExclusively () {throw new IllegalMonitorStateException ();} // obtain the condition queue header Node first = firstWaiter; if (first! = Null) {// wake up all nodes of the condition queue doSignalAll (first) ;}// wake up all nodes of the condition queue private void doSignalAll (Node first) {// set the reference of the header and tail nodes to null. lastWaiter = firstWaiter = null; do {// obtain the reference of the next Node first = first. nextWaiter; // leave the first parameter empty for subsequent references of the node to be transferred. nextWaiter = null; // transfers the node from the condition queue to the synchronization queue transferForSignal (first); // points the reference to the next node first = next;} while (first! = Null );}
So far, our entire AbstractQueuedSynchronizer source code analysis is over. I believe that through these four analyses, you can better understand AQS. This class is really important, because it is the cornerstone of many other synchronization classes. Due to the limited level and expression ability of the author, if there is no clear description or understanding, we also ask readers to correct them in time to discuss and learn together. You can read the questions you encounter in the following message. If you need to comment out the source code of AQS, contact the author for help.
Note: All the above analyses are based on JDK1.7, which may vary with different versions.
The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.