1 introduction

Condition is used when a thread waiting/wake operation, the implementation is AbstractQueuedSynchronizer ConditionObject, sample code:

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

lock.lock();
try {
   condition.await();
   // operations
   condition.signal();
}finally {
   lock.unlock();
}
Copy the code

Note:

  • Condition object passesLock.newCondition()Create;
  • Must be done after lock. Lock ()Condition.awaitOperation, for reasons discussed later in this article.

Condition is realized based on AQS, and AQS can refer to AQS source code analysis and core method analysis.

2 attribute

    private transient Node firstWaiter; // Wait for the first node of the queue
        
    private transient Node lastWaiter; // Wait for the last node in the queue
Copy the code

Condition Node, only Condition /CANCELLED.

3 Internal Methods

3.1 addConditionWaiter

Function: Constructs a new Node using the current thread and the waitStatus value node. CONDITION and adds it to the end of the CONDITION queue.

Returns: new node added

Process:

  • 1 if thelastWaiter==null, indicating that the queue is not initializedfirstWaiter = node;
  • 2 if the originallastWaiter! =null, is the originallastWaiter.nextWaiter = node;
  • 3 in the end,lastWaiter = node, point the lastWaiter field to node.
        private Node addConditionWaiter(a) {
            Node t = lastWaiter;
            /* * If lastWaiter is not a CONDITION, cancel all CANCELLED nodes in the conditional queue. * lastWaiter will change after cleaning. * /
            if(t ! =null&& t.waitStatus ! = Node.CONDITION) { unlinkCancelledWaiters(); t = lastWaiter; }// Create a new node. The initial waitStatus of the node is CONDITION
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            
            If t == null, firstWaiter refers to the new node; Otherwise, t.netwaiter points to the new node, which adds the new node to the end of the queue. * /
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            
            // Finally, the lastWaiter attribute points to the new node node
            lastWaiter = node;
            return node;
        }
Copy the code

3.2 unlinkCancelledWaiters

Run the following command to clear all cancelled nodes in the conditional queue:

Process:

  • 1 Start with firstWaiter and add the entire listt.waitStatus ! = Node.CONDITIONThe node of is removed;
  • After 2 nodes are removed, place them in front of the nodenextWaiterPoints to the rear node.
 private void unlinkCancelledWaiters(a) {
            Node t = firstWaiter;
            Node trail = null;
            while(t ! =null) {
                Node next = t.nextWaiter;
            
                if(t.waitStatus ! = Node.CONDITION) {CONDITION: * The nextWaiter of the current node is set to null. * If trail is empty, firstWaiter points to the previously saved t.nextwaiter, * otherwise trail.nextwaiter points to the previously saved T.nextwaiter. * /
                
                    t.nextWaiter = null;
                    if (trail == null)
                        firstWaiter = next;
                    else
                        trail.nextWaiter = next;
                    if (next == null)
                        lastWaiter = trail;
                }
                elsetrail = t; t = next; }}Copy the code

3.3 fullyRelease

Call release(to release the lock) with the current state as an argument.

Return: Release the previous value of state.

Process:

  • 1 throughgetState()Get current state;
  • 2 performrelease(int);
  • 3 The state value before the release is displayed on success, and the state value before the release is displayed on failurenode.waitStatus = Node.CANCELLED.
    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw newIllegalMonitorStateException(); }}finally {
            if(failed) node.waitStatus = Node.CANCELLED; }}Copy the code

3.4 isOnSyncQueue

Function: Check whether a node is in the synchronization queue.

    final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            // waitStatus is CONDITION, or prev is null
            return false;
        if(node.next ! =null) 
            // As described earlier in this article for Node, next will only have a value in the synchronization queue.
            return true;
        /* Node. prev is not null, but is not in the synchronization queue because CAS may fail to replace values. So we need to go through it again from the back of the line. * /
        return findNodeFromTail(node);
    }

    /** Start from tail on the synchronization queue and walk forward to see if there are nodes. * /
    private boolean findNodeFromTail(Node node) {
        Node t = tail;
        for (;;) {
            if (t == node)
                return true;
            if (t == null)
                return false; t = t.prev; }}Copy the code

3.5 transferForSignal

Function: Change a Condition queue Node to a synchronization queue Node, and then add the Node to the synchronization queue. Finally, change the Node’s waitStatusCAS in the synchronization queue to Node.SIGNAL.

Returns: the node argument is in the cancelled state (waitStatus! CONDITION) returns false, otherwise returns true.

Process:

  • CONDITION set waitStatus from node. CONDITION to 0. False is returned if this fails.
  • 2 Call the ENQ method to add the node to the end of the synchronization queue and return the node in front of the synchronization queue.
  • 3. If waitStatus of the front-node is CANCELLED, the thread held by the node is awakened;
  • 4 Otherwise, rightFront nodesthewaitStatusThe CAS forNode.SIGNAL, wakes up the thread held by the node.

    final boolean transferForSignal(Node node) {
        
        // Replace Node.waitStatus with node. CONDITION by CAS to 0, false on failure.
        if(! compareAndSetWaitStatus(node, Node.CONDITION,0))
            return false;

        /* Node joins the synchronization queue; The return value p is the end of the synchronization queue, which means node is now the front node in the synchronization queue. * /
        Node p = enq(node);
        int ws = p.waitStatus;
        
        /* ws is the pre-node waitStatus of node in the synchronization queue; Ws > 0 indicates that P is CANCELLED; When ws <= 0, CAS is executed to replace the status of p with Node.signal. If P is CANCELLED, or CAS fails as Node.SIGNAL, unpark the thread of the current Node. * /
        if (ws > 0| |! compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread);return true;
    }
Copy the code

3.6 doSignal

Run the transferForSignal command to convert the first parameter to a Node in the synchronization queue (waitStatus==0) and add it to the synchronization queue. At the same time, set waitStatus in the synchronization queue to Node.signal.

Process:

  • 1 Assign first nextWaiter to firstWaiter;
  • 2 Call transferForSignal(first).
  • 3 If the transferForSignal succeeds, the process ends. Otherwise, assign the previous firstWaiter(pointing to the previous first.nextwaiter) to first to recirculate.
        private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while(! transferForSignal(first) && (first = firstWaiter) ! =null);
        }
Copy the code

4 await

4.1 Execution Process

  • 1 performaddConditionWaiter()Create a new CONDITION state node and joinEnd of conditional queueAnd returns the node;
  • 2 performfullyReleaseRelease the current state value (that is, set it to 0) and save the previous state value to savedState; If this fails, the new node waitStatus is CANCELLED;
  • 3 byisOnSyncQueueTo check whether the queue is in the synchronization queuepark;
  • 4 After being unpark by another thread, the savedState value is usedacquireQueued(node, savedState).
 
        public final void await(a) throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            
            // Add a new node to the end of the queue
            Node node = addConditionWaiter();
            / / release of the state
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            
            // The node is not in the synchronization queue
            while(! isOnSyncQueue(node)) {//park
                LockSupport.park(this);
                if((interruptMode = checkInterruptWhileWaiting(node)) ! =0)
                    break;
            }
            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

4.2 Lock.lockThe ability afterawait?

In the fullyRelease of the Condition. Await call, release will be called, and tryRelease will be called. In the ReentrantLock, tryRelease will be determined as follows:

if(Thread.currentThread() ! = getExclusiveOwnerThread())throw new IllegalMonitorStateException();
Copy the code

Before executing lock. Lock (), getExclusiveOwnerThread() returns null, so:

Condition. Await can only be executed after lock. Lock.

5 signal

  • Checks whether the current thread has exclusive access to the lock, and executes if it doesdoSignal;
  • isHeldExclusivelyinReentrantLockThe implementation ofreturn getExclusiveOwnerThread() == Thread.currentThread();.
        public final void signal(a) {
            if(! isHeldExclusively())throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if(first ! =null)
                doSignal(first);
        }
Copy the code