This is the 11th day of my participation in the August More Text Challenge. For details, see:August is more challenging

preface

The CLH synchronization queue was introduced earlier, but the AQS inner class also has a ConditionObject. A conditional queue is the queue maintained by ConditionObject.

Contition is a generalized conditional queue that uses await() and signal() to provide a more flexible wait/notification mode for threads.

1. Condition interfaces

We know that any Object has a set of monitor methods defined in the Java.lang. Object class. The main methods are wait(), wait(Long Timeout), notify(), and notifyAll(). These methods can be used in conjunction with the synchronized keyword to implement wait/notify patterns.

The Condition interface also provides object-like monitor methods that work with locking to implement the wait/notify pattern. But there are differences in usage and features.

Compare Object’s monitor methods to the Condition interface

1.1 Condition Queue

Condition must be used in conjunction with a Lock because access to a shared state variable occurs in a multithreaded environment.

public class ConditionUseTest {

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        Thread thread1 = new Thread(()->{
            String name = Thread.currentThread().getName();
            lock.lock();
            System.out.println(name + "==> Successfully acquired lock" + lock);
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + "==> Awaken");
            lock.unlock();
            System.out.println(name + "==> Release lock");

        }, Thread 1 "");
        thread1.start();

        Thread thread2 = new Thread(()->{
            String name = Thread.currentThread().getName();
            lock.lock();
            System.out.println(name + "==> Successfully acquired lock" + lock);
            try {
                System.out.println("= = = = = = = = = = 1: thread condition queue await, not be signal will have been waiting for = = = = = = = = = = =");
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + "==> Notify waiting queue threads");
            condition.signal();
            lock.unlock();
            System.out.println(name + "==> Release lock");

        }, Thread 2 ""); thread2.start(); }}Copy the code

Execution result:

1.2 Source code Analysis

AQS, Lock,Condition, ConditionObject

In the case of ReentrantLock, ConditionObject is an internal AQS class that implements the Condition interface. Lock provides newCondition() and delegates it to Sync, the internal AQS implementation, to create ConditionObject. Enjoy AQS support for Condition.

As you can see from the above example, the conditional queue process is something like lock -> await -> signal -> unlock

WaitStatus can take five states:

  1. It’s initialized to 0, which means nothing, and then it’s set to signal.
  2. 1 indicates cancelled, indicating that the lock is cancelled by the current thread.
  3. -1 indicates signal. After the current node releases the lock, it needs to wake up subsequent nodes that can be woken up.
  4. -2 indicates condition, the focus of this article, which indicates that the current node is in the condition queue.
  5. -3 indicates propagate, which means that when the shared resource is released, it will propagate back to release other shared nodes.

1.2.1 Waiting 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 condition.await (), the thread will release the lock and construct the node to join the wait queue and enter the wait state.

Condition has a reference to both the first and last nodes, and the new node simply points to the original nextWaiter and updates the last node.

The relationship between the synchronization queue and wait queue

The implementation of the Condition is an inner class of the synchronizer, so each instance of the Condition has access to the methods provided by the synchronizer. In effect, each Condition has a reference to the synchronizer to which it belongs

1.2.2 waiting

Await the code

public final void await(a) throws InterruptedException {
    // 1. The current thread is interrupted and an exception is thrown
    if (Thread.interrupted())
        throw new InterruptedException();
    // 2. Add to the conditional queue
    Node node = addConditionWaiter();
    // 3. Release synchronization resources and lock them
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 4. If the thread of this node is not in the synchronization queue, it is not qualified to compete for the lock
    while(! isOnSyncQueue(node)) {// Suspend the thread
        LockSupport.park(this);
        // The thread interrupts and exits
        if((interruptMode = checkInterruptWhileWaiting(node)) ! =0)
            break;
    }
    // Exit the loop, wake up, and enter the blocking queue waiting to acquire the lock acquireQueued
    if(acquireQueued(node, savedState) && interruptMode ! = THROW_IE) interruptMode = REINTERRUPT;if(node.nextWaiter ! =null) // clean up if cancelled
        unlinkCancelledWaiters();
    if(interruptMode ! =0)
        reportInterruptAfterWait(interruptMode);
}
Copy the code

Conditionwaiter () adds conditionwaiter () to the conditional queue

private Node addConditionWaiter(a) {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if(t ! =null&& t.waitStatus ! = Node.CONDITION) {// Iterate through the entire conditional queue, clearing all cancelled nodes out of the column
        unlinkCancelledWaiters();
        // re-assign t
        t = lastWaiter;
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    // t == null indicates that the queue is empty
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    // Put the tail pointer to the new node
    lastWaiter = node;
    return node;
}
Copy the code

Void unlinkCancelledWaiters() clears a node that has been cancelled from the queue

private void unlinkCancelledWaiters() { Node t = firstWaiter; // Trail = null; while (t ! = null) { Node next = t.nextWaiter; if (t.waitStatus ! = Node.CONDITION) { t.nextWaiter = null; if (trail == null) firstWaiter = next; else trail.nextWaiter = next; if (next == null) lastWaiter = trail; } else trail = t; t = next; }}Copy the code

1.2.3. Notice

A call to the Condition’s signal() method wakes up the node that has been waiting the longest (the first node) in the wait queue, and moves the node to the synchronization queue before waking it up.

The precondition for calling this method is that the current thread must have acquired the lock. You can see that the signal() method performs the isHeldExclusively() check, which means that the current thread must be the thread that has acquired the lock. It then takes the first node of the wait queue, moves it to the synchronization queue and wakes up the thread in the node using LockSupport.

Reference documentation

AQS Series 4: Condition queue analytical explanation Condition source queue, signal and await the Java Condition source line source code analysis clear AbstractQueuedSynchronizer arts of concurrent Java programming Java Concurrent Programming In Action