This article is compiled from chapter 5 of The Art of Java Concurrent Programming by Fang Tengfei, Wei Peng and Cheng Xiaoming
AQS:AbstractQueuedSynchronizer
ConditionObject is the inner class of the synchronizer AQS, and Condition requires the lock associated with it. Each Condition object contains a queue (hereafter referred to as a wait queue), which is the key to the Condition object’s wait/notification function.
The implementation of Condition will be analyzed below, including: wait queue, wait and notification. The Condition mentioned below, if not specified, refers to ConditionObject.
1. Wait queue
The wait queue is a FIFO queue. Each node in the queue contains a thread reference, which is the thread waiting on the Condition object. If a thread calls the condition.await () method, the thread will release the lock, construct the node to join the wait queue, and enter the wait state. Nodes, in fact, the definition of reuse the definition of synchronizer of Node, that is to say, the synchronous Node types in the queue and waiting queue are AbstractQueuedSynchronizer synchronizer of a static inner class. The Node.
A Condition contains a wait queue, and the Condition has a first node (firstWaiter) and a last node (lastWaiter). The current thread calls the condition.await () method, which constructs the node from the current thread and adds the node to the wait queue from the tail. The basic structure of the wait queue is shown in Figure 5-9.
As shown in the figure, the Condition has a reference to the first and last nodes, and the new node simply points the original nextWaiter to it and updates the last node. The procedure of referring to the update above does not use CAS guarantees because the thread calling the await() method must be the one that has acquired the lock, that is, the procedure is thread-safe by the lock.
In the monitor model of Object, an Object has a synchronization queue and a wait queue, while a Lock (or more specifically, a synchronizer) in a parallel packet has a synchronization queue and multiple wait queues, as shown in Figure 5-10.
2, waiting for
Calling the await() method of Condition (or a method beginning with await) causes the current thread to queue and release the lock while the thread state changes to wait. When returned from await(), the current thread must have acquired the lock associated with Condition.
If the await() method is viewed from the perspective of the queue (synchronous queue and wait queue), when the await() method is called, it corresponds to the head node of the synchronous queue (the node that has acquired the lock) being moved to the wait queue of the Condition.
The await() method of Condition looks like this:
public final void await() throws InterruptedException {
if(Thread.interrupted()) throw new InterruptedException(); // the current thread joins the queue. 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) unlinkCancelledWaiters();if(interruptMode ! = 0) reportInterruptAfterWait(interruptMode); }Copy the code
The thread that calls this method has successfully acquired the lock thread, that is, the first node in the synchronization queue. This method constructs the current thread as a node and joins the wait queue, then releases the synchronization state and wakes up the successor nodes in the synchronization queue, and then the current thread enters the wait state.
When a node in the wait queue is awakened, the thread that awakened the node attempts to obtain synchronization status. If the waiting thread is interrupted instead of being awakened by another thread calling the conditional.signal () method, InterruptedException is thrown.
From the perspective of queues, the current thread joins the Condition’s waiting queue, as shown in Figure 5-11.
As shown in the figure, the first node of the synchronous queue does not join the wait queue directly, but instead constructs the current thread as a new node and adds it to the wait queue using the addConditionWaiter() method.
3, notifications,
Calling the signal() method of Condition wakes up the node that has waited the longest in the wait queue (the first node) and moves the node to the synchronization queue before waking it up.
The signal() method for Condition is shown in Listing 5-23.
public final void signal() {//isHeldExclusively() AQS subclass implementationif(! isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter;if(first ! = null)doSignal(first);
}
Copy the code
The precondition for calling this method is that the current thread must have acquired the lock, and you can see that the signal() method does an isHeldExclusively() check that the current thread must be the one that acquired the lock. It then gets the head node of the wait queue, moves it to the synchronization queue and wakes up the thread in the node using LockSupport.
Figure 5-12 shows the process of moving a node from the wait queue to the synchronization queue.
The awakened thread exits from the while loop in the await() method (isOnSyncQueue(Node Node) method returns true, the Node is already in the synchronization queue) and calls the synchronizer’s acquireQueued() method to join the race to get the synchronization state.
After successfully obtaining the synchronization state (or lock), the awakened thread will return from the await() method called earlier, at which point it has successfully obtained the lock.
Condition’s signalAll() method is equivalent to executing signal() once for each node in the wait queue. The effect is to move all nodes in the wait queue to the synchronous queue and wake up the thread of each node.
4. Blogger information
Personal wechat official Account:
Personal blog
Individuals making
Personal gold digger blog
Personal CSDN blog