The implementation of concurrent Java toolkit AbstractQueuedSynchronizer. ConditionObject points

This paper explains the logic of ConditionObject, summarizes the key points and forms the key points. Make sure you don’t have to read ConditionObject’s source code again and that you already know the implementation details in depth, so you’ll be able to write code, conduct interviews, and analyze questions later on.

1. First, conditions of queue ConditionObject as AbstractQueuedSynchronized (hereinafter referred to as “AQS”) of the static inner class, by AQS. This refer to external class instance (figure 1). Why do we need this correlation? Because multiple threads may concurrently call await(), await() needs to modify the shared conditional queue (a singly linked list and a synchronous queue on the AQS to move thread nodes between the two queues. ConditionObject is linked in this manner to the external AQS class which provides the lock function required to ensure thread-safe concurrent access. ConditionObject therefore requires an AQS lock to modify Condition and synchronous queues exclusively, and ConditionObject doesn’t consider modifying the shared data internally, which is why ConditionObject must hold the lock before using Condition.

Secondly, AQS provides a synchronization queue (used to implement the lock), while ConditionObject provides a conditional queue, and AQS can associate multiple conditional queues, as shown in the figure below. A thread node in a synchronous queue is competing for the lock, while a thread waiting in a conditional queue is not competing for the lock, but is waiting for a signal from another thread, which calls signal().

3. Await operation: the thread will call await() after it has obtained the lock, so there will be no node corresponding to the current thread on the synchronous queue. In the await() operation, the current thread node is first queued and then the lock is released. When the lock is released, the state of the lock is saved to the stack of the current thread (local variable). After the lock is released, a wake up action (done by locksupp.unpark, which wakes up one thread in the synchronization queue) is performed to give other threads a chance to compete for the AQS lock. The current thread enters a loop that continuously determines whether it is on the synchronization queue (it was not at the time of the call) and blocks itself if it is not; If so, it jumps out of the loop (because other threads call Signal, which cleverly puts this thread back on the synchronous queue). When out of the loop, compete for AQS locks. Await () returns normally only after AQS lock has been obtained. Notice that the lock has been reheld after await() returns. The process is as follows:

4. Signal operation: The wakeup thread must first hold the AQS lock and then call signal() in order to safely operate on synchronous and conditional queues. Signal () first finds the first node of the conditional queue, a, takes it out of the queue, changes its state to conditional wait (waitStatus=2), and then puts it on the synchronous queue of the AQS. It then wakes up the thread or leaves it blocked depending on the lock waiting status (node B before node A in the synchronous queue). During the above operation, if the thread corresponding to node A performs the cancellation action (such as by interrupt, timing), then it finds the next node in the conditional queue and continues with the above steps. In the case of signal(), only one thread node is successfully transferred to end; For signalAll(), all thread nodes need to be moved to the synchronization queue. Note that the lock is not released after signal() and signalAll() are executed.

5. Other await operations, such as awaitUninterruptibly, awaitNanos, awaitUntil, etc. are almost the same as await. The difference lies in how the loop handles interrupts, timing, etc. when the blocking operation temporarily returns.

Differences between Lock and Condition

Lock has spin and Condition has no spin. Because lock locking is unconditional, spin reduces thread context switching; Condition, on the other hand, actively blocks when it detects that a Condition is not met. Therefore, spin has little meaning for Condition, because if the Condition is not satisfied, there is a high probability that it will not be satisfied for a short period of time.

Queue operations in Lock require a lot of careful CAS operations because there is no Lock protection; Condition, on the other hand, is protected by the lock, so there is no need to consider synchronization when moving nodes between the conditional queue and the lock queue. The CAS operation is required only when the wait state is changed to deal with the sudden cancellation of the wait.

conclusion

The finish see ConditionObject after the implementation of the key points are you running and operation of Condition has more in-depth experience. To summarize, CondtionObject is associated with an AQS object and has a single linked list inside which all waiting threads wait. Conditional waits and wakes are implemented by moving nodes representing threads between conditional queues and lock queues. When a thread calls await on the Condition, it puts itself on the Condition queue and blocks, which is done in a loop alternately blocking (locksupport.park) and checking whether it is on the lock queue. When a thread calls signal on the Condition, it is moved from the Condition to the lock queue of the AQS with which it is locked, and it is woken up. (At this point, the Condition for the blocking loop is met, causing the loop to jump out.)