Abstractqueuedsynchronizer source code analysis of Java concurrency series (conditional queue)

Through the analysis of the previous three articles, we have a deep understanding of the internal structure and some design concepts of abstractqueuedsynchronizer, and know that abstractqueuedsynchronizer maintains a synchronization state and two queuing areas, which are synchronization queue and condition queue respectively. We still take the public toilet as an analogy. The synchronous queue is the main queue area. If the public toilet is not open, all people who want to enter the toilet have to queue here. The conditional queue is mainly set for conditional waiting. Let's imagine that if a person finally successfully obtains the lock and enters the toilet through queuing, but finds that he doesn't bring toilet paper before convenience, although he is helpless, he must also accept the fact. At this time, he has to go out and prepare toilet paper first (enter the conditional queue and wait), Of course, the lock must be released before going out so that others can come in. After the toilet paper is ready (the conditions are met), it has to go back to the synchronization queue again. Of course, not all people entering the room do not have toilet paper. There may be other reasons why they must interrupt the operation and queue in the condition queue first. Therefore, there can be multiple condition queues, and different condition queues can be set according to different waiting conditions. The condition queue is a one-way linked list. The condition interface defines all operations in the condition queue. The conditionobject class inside abstractqueuedsynchronizer implements the condition interface. Let's see what operations the condition interface defines.

Although so many methods are defined in the condition interface, they are divided into two categories. The method starting with await is the method that the thread enters the condition queue and waits, and the method starting with signal is the method that "wakes up" the thread in the condition queue. It should be noted here that calling the signal method may or may not wake up the thread. When the thread will wake up depends on the situation, which will be discussed later, but calling the signal method will certainly move the thread from the condition queue to the end of the synchronization queue. For the convenience of description, let's not tangle so much for the time being. We uniformly call the signal method the operation of waking up the conditional queue thread. Please note that there are five await methods: response thread interrupt waiting, non response thread interrupt waiting, setting relative time non spin waiting, setting relative time spin waiting, and setting absolute time waiting; There are only two kinds of signal methods: wake up only the head node of the conditional queue and wake up all nodes of the conditional queue. The methods of the same kind are basically the same. Due to space constraints, we can't and don't need to talk about all these methods carefully. We only need to understand one representative method and then look at other methods. Therefore, in this article, I will only talk about the await method and signal method in detail. Other methods will not be discussed in detail, but the source code will be posted for your reference.

1. Wait for response to thread interrupt

When a thread calls the await method, it will first wrap the current thread as a node node and put it at the end of the condition queue. In the addconditionwaiter method, if it is found that the end node of the condition queue has been cancelled, it will call the unlinkcanceledwaiters method to empty all the cancelled nodes of the condition queue. This step is the preparation for inserting the node. After ensuring that the state of the tail node is also condition, a new node 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

Wrap the current thread as a node and add it to the end of the condition queue, and then call the fullyrelease method to release the lock. Note that the method name is fullyrelease, which will completely release the lock. Because the lock is reentrant, you need to release all the locks before conditional waiting, otherwise others will not be able to obtain the lock. If the lock release fails, a runtime exception will be thrown. If the lock is successfully released, the previous synchronization state will be returned.

Step 3: wait conditionally

After the above two operations are completed, you will enter the while loop. You can see that locksupport is called first in the while loop Park (this) suspends the thread, so the thread will always block here. After calling the signal method, only the node is transferred from the condition queue to the synchronization queue. Whether the thread will wake up depends on the situation. If it is found that the predecessor node in the synchronization queue has been cancelled when transferring the node, or it fails to update the status of the predecessor node to signal, the thread will wake up immediately. Otherwise, when the signal method ends, the thread already in the synchronization queue will not wake up, but wait until its predecessor node wakes up. Of course, in addition to calling the signal method to wake up, the thread can also respond to interrupts. If the thread receives an interrupt request here, it will continue to execute. You can see that after waking up, the thread will immediately check whether it wakes up due to an interrupt or through the signal method. If it wakes up due to an interrupt, the node will also be transferred to the synchronization queue, but it is realized by calling the transferaftercanceledwait method. Finally, after this step, the interrupt condition will be returned and the while loop will jump out.

Step 4: operation after the node moves out of the condition queue

When the thread terminates the while loop, that is, conditional waiting, it will return to the synchronization queue. Whether it is due to calling the signal method or thread interruption, the node will eventually be in the synchronization queue. At this time, the acquirequeueueued method will be called to obtain the lock in the synchronization queue. This method has been described in detail in the exclusive mode. In other words, after the node comes out of the condition queue, it obediently takes the set of obtaining the lock in the exclusive mode. After the node obtains the lock again, it will call the reportinterruptafterwait method to make a corresponding response according to the interruption during this period. If the interrupt occurs before the signal method, the interruptmode is throw_ Ie, throw an exception after obtaining the lock again; If the interrupt occurs after the signal method, the interruptmode will be reinterrupt. After obtaining the lock again, it will be interrupted again.

2. Wait without responding to thread interrupt conditions

3. Set the conditional waiting of relative time (no spin)

4. Set the conditional waiting of relative time (spin)

5. Set the waiting condition of absolute time

6. Wake up the head node in the condition queue

It can be seen that the final core of the signal method is to call the transferforsignal method. In the transferforsignal method, CAS operation will be used to set the state of the node from condition to 0, and then enq method will be called to add the node to the end of the synchronization queue. We can see the following if judgment statement. This judgment statement is mainly used to judge when the thread will be awakened. In these two cases, the thread will be awakened immediately. One is when it is found that the state of the previous relay node is cancelled, and the other is when it fails to update the state of the previous relay node. In both cases, the thread will wake up immediately. Otherwise, it will just transfer the node from the condition queue to the synchronization queue, and will not wake up the thread in the node immediately. The signalall method is similar, except that it circulates through all nodes in the condition queue and transfers them to the synchronization queue. The method of transferring nodes also calls the transferforsignal method.

7. Wake up all nodes of the conditional queue

So far, our entire abstractqueuedsynchronizer source code analysis is over. I believe that through the analysis of these four articles, we can better master and understand AQS. This class is really important because it is the cornerstone of many other synchronous classes. Due to the author's limited level and expression ability, if there is no clear expression or understanding, please correct it in time and discuss and learn together. You can leave a message below to read the problems encountered. If you need AQS annotation source code, you can also contact the author to obtain it.

Note: all the above analysis is based on jdk1 7. There will be differences between different versions, which readers should pay attention to.

The above is the whole content of this article. I hope it will be helpful to your study, and I hope you can support programming tips.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>