Introduction of Condition

The Object class is the parent of all classes in Java, and several methods are often applied to Object to achieve communication between threads: Wait (),wait(long timeout),wait(long timeout),wait(long timeout, int nanos) and notify() implement the wait/notification mechanism. The same approach is used to implement wait/notification under Java Lock. On the whole, Object’s wait and notify/notify are wait /notify mechanisms that work with Object monitor to complete the wait /notify mechanism. Condition and Lock work with each other to complete the wait /notify mechanism. The former is Java low-level, while the latter is language-level and has higher controllability and expansibility. In addition to the differences in the use of the two, there are many differences in the functions and features:

  • Condition can support not responding to interrupts, but not by using Object

  • Condition supports multiple wait queues (new multiple Condition objects), while Object supports only one

  • Condition supports timeout while Object does not

Referring to Object’s wait and notify/notifyAll methods, Condition provides the same method:

  • Wait methods for objects

Void await() throws InterruptedException// The current thread enters the wait state, Long awaitNanos(Long nanosTimeout)// The current thread enters the wait state until notified, Boolean await(long time, TimeUnit unit)throws InterruptedException Support custom time units Boolean awaitUntil(Date deadline) throws InterruptedException// The current thread enters the wait state until it is notified, interrupted, or reaches a certain timeCopy the code
  • Notify /notifyAll for objects

Void signal()// Wakes up a thread waiting on condition and moves it from the wait queue to the synchronous queue, or from the wait method if it can compete for Lock in the synchronous queue. Void signalAll()// Differs from 1 in that it wakes up all threads waiting on conditionCopy the code

Condition realization principle analysis

Waiting queue

ConditionObject is created using lock.newCondition(), which actually creates ConditionObject, which is an inner class of AQS. Condition is used in conjunction with Lock, that is, Condition and Lock are bound together, and the implementation principle of Lock depends on AQS. Naturally ConditionObject is an internal class of AQS. As we know, AQS maintains a synchronous queue internally. If it is an exclusive lock, the tails of all threads that fail to acquire the lock are inserted into the synchronous queue. Similarly, Condition maintains a waiting queue internally in the same way. All threads calling the condition.await method are queued and the thread state transitions to wait state. Note also that ConditionObject has two member variables:

/** First node of condition queue. */private transient Node firstWaiter; /** Last node of condition queue. */private transient Node lastWaiter;Copy the code

ConditionObject manages wait queues by holding the leading and trailing Pointers to them. Note that the Node class overrides the Node class in AQS. The Node class has a property like this:

// Node nextWaiter;Copy the code

The wait queue is a one-way queue, whereas the synchronous queue was known to be a two-way queue when we said AQS earlier.

Schematic diagram of waiting queue:


Note: We can call the lock.newCondition() method multiple times to create multiple Condition objects, meaning that an lLock can hold multiple wait queues. The Object approach actually means that there can only be one synchronization queue and one wait queue on the Object monitor; And the Lock in the packet has a synchronization queue and multiple wait queues. The schematic diagram is as follows:


ConditionObject is an internal class of AQS, so each Condition has access to the methods provided by AQS, which means that each Condition has a reference to its synchronizer.

Implementation principle of await

Calling condition.await() causes the thread currently obtaining the lock to wait. If the thread can return from await(), it must have obtained the lock associated with condition. Await () method source:

public final void await() throws InterruptedException {    if(Thread.interrupted()) throw new InterruptedException(); // 1. Wrap the current thread as Node and insert it into the queue. Int savedState = fullyRelease(node); savedState = fullyRelease(node); int interruptMode = 0;while(! IsOnSyncQueue (node)) {// 3. The current thread enters the wait state locksupport.park (this);if((interruptMode = checkInterruptWhileWaiting(node)) ! = 0)break; } // 4. Spin wait to obtain synchronization state (i.e. obtain lock)if(acquireQueued(node, savedState) && interruptMode ! = THROW_IE) interruptMode = REINTERRUPT;if(node.nextWaiter ! = null) // clean upifcancelled unlinkCancelledWaiters(); // 5. Handle the interrupted situationif(interruptMode ! = 0) reportInterruptAfterWait(interruptMode); }Copy the code

A call to condition.await() by the current thread causes the current thread to release the lock and join the wait queue. A call to condition.await() causes the current thread to move from the wait queue to the synchronization queue until it is signalled /signalAll, and then returns from await method until the lock is obtained. Or interrupt processing if interrupted while waiting.

AddConditionWaiter () adds the current thread to the queue.

private Node addConditionWaiter() {    Node t = lastWaiter;    // If lastWaiter is cancelled, clean out.
    if(t ! = null && t.waitStatus ! = Node.CONDITION) { unlinkCancelledWaiters(); t = lastWaiter; } // wrap the currentThread into Node Node Node = new Node(thread.currentthread (), node.condition);if(t ==null) //t==null, firstWaiter = node;else// end insert method t.netwaiter = node; // update lastWaiter = node;return node;
}Copy the code

Here, the Node encapsulated by the current thread is inserted into the wait queue by the tail insertion method. Meanwhile, it can be seen that the wait queue is a chain queue without leading nodes. When we learned AQS before, we knew that the synchronous queue is a chain queue with leading nodes.

After inserting the current node into the wait column, use fullyRelease(0) to release the current thread lock.

final int fullyRelease(Node node) {    boolean failed = true;    try {        int savedState = getState();        if(release(savedState)) {// Release synchronization state failed =false;            return savedState;
        } else{/ / not successful release synchronous state throw new exception IllegalMonitorStateException (); } } finally {if(failed) node.waitStatus = Node.CANCELLED; }}Copy the code

The AQS template method release() is called to release the synchronization state of the AQS and wake up the threads referenced by the successor nodes of the header in the synchronization queue. If the release succeeds, the thread will return normally, and if it fails, an exception will be thrown.

How do I exit from the await() method? The await() method has this code:

while(! IsOnSyncQueue (node)) {// 3. The current thread enters the wait state locksupport.park (this);if((interruptMode = checkInterruptWhileWaiting(node)) ! = 0)break;
}Copy the code

When the thread first calls the condition.await() method, it enters the while() loop, and then locksupport.park (this) makes the current thread wait. To exit the await method, it exits the while loop first. There are two ways to exit the while loop:


  1. Break exits the while loop


  1. The logic in the while loop says false

The condition of the first case is that the current waiting thread will go to break and exit after being interrupted.

The second case is when the current node is moved to a synchronous queue (that is, the signal or signalAll method of condition called by another thread) and the while loop terminates when the logic in the while determines false.

AcquireQueued (Node, savedState) is called when exiting the while loop. This method attempts to obtain the synchronization state during the spin until it succeeds (the thread obtains the lock).

This means that the exit await method must be a Lock that has obtained a Condition reference (association).

The await method is shown as follows:


The thread calling condition. Await must have acquired the LOCK, that is, the current thread is the head of the synchronization queue. Calling this method causes the Node tail wrapped by the current thread to be inserted into the wait queue.

Timeout mechanism support

Condition also supports a timeout mechanism, and the user can invoke methods awaitNanos,awaitUtil. The implementation principle of these two methods is basically the same as tryAcquire method in AQS.

Does not respond to interrupt support

Call condition. AwaitUninterruptibly () method, the method of the source code for:

public final void awaitUninterruptibly() {    Node node = addConditionWaiter();    int savedState = fullyRelease(node);    boolean interrupted = false;    while(! isOnSyncQueue(node)) { LockSupport.park(this);if (Thread.interrupted())
            interrupted = true;
    }    if (acquireQueued(node, savedState) || interrupted)
        selfInterrupt();
}Copy the code

Basically the same as the await method above, except that the interrupt handling is reduced and the reportInterruptAfterWait method is omitted to throw interrupted exceptions.

Implementation principles of signal and signalAll

Calling the signal or signalAll methods of Condition moves the longest waiting node to the synchronous queue, giving that node a chance to acquire the lock. The condition signal method is called to move the head node to the synchronous queue. The condition signal method is called to move the head node to the synchronous queue. Signal () :

public final void signal() {//1. Check whether the current thread has acquired the lockif(! isHeldExclusively()) throw new IllegalMonitorStateException(); // first = firstWaiter; // first = first waiter;if(first ! = null)doSignal(first);
}Copy the code

The signal method first checks whether the current thread has acquired the lock. If it has not acquired the lock, it will throw an exception. If it has obtained the lock, it will get the node referenced by the head pointer of the waiting queue, which is also based on the node. The doSignal method is as follows:

private void doSignal(Node first) {    do {        if( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; First. nextWaiter = null; / / 2.whileThe transferForSignal method does the real processing on the header}while(! transferForSignal(first) && (first = firstWaiter) ! = null); }Copy the code

TransferForSignal () is the method that handles the opposite node.

final boolean transferForSignal(Node node) { //1. Update status to 0if(! compareAndSetWaitStatus(node, Node.CONDITION, 0))return false; //2. Move the Node to the synchronization queue Node p = enq(Node); int ws = p.waitStatus;if(ws > 0 || ! compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread);return true;
}Copy the code

This code does two things:

  • 1. Change the state of the header to CONDITION

  • 2. Invoke the enQ method to insert the end of the node into the synchronization queue

The condition’s signal is called only if the current thread has acquired the lock. This method causes the head node of the waiting queue (the one that has waited the longest) to move to the synchronous queue, which then has a chance to wake up the waiting thread. Return from the locksupport.park (this) method in the await method so that the thread calling the await method has a chance to exit successfully.

The signal method is shown as follows:


signalAll

The difference between sigllAll and SIGal methods is reflected in the doSignalAll method. DoSignalAll () ¶

private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;    do {        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while(first ! = null); }Copy the code

The doSignal method only operates on the head node of the wait queue, while the doSignalAll method moves every node in the wait queue into the synchronous queue, “notifying” every thread that is currently calling the condition.await() method.

Await combined with signal and signalAll

The await and signal and signalAll methods are like A switch controlling thread A (waiting party) and thread B (notifying party). The relationship between them can be better illustrated by the following diagram:


The awaitThread first obtains the lock from lock.lock() and then calls condition. Await to queue. The other thread, signalThread, calls condition. Signal or signalAll after acquiring the lock successfully, giving the awaitThread the opportunity to move to the synchronization queue. This gives the awaitThread a chance to obtain the lock after another thread releases the lock, so that the awaitThread can exit from the await method and perform subsequent operations. If the awaitThread fails to acquire the lock, it is directly put into the synchronization queue.