AQS related components of java concurrency Condition

What is Condition

  • Condition is an interface
  • It provides a method analogous Object of the monitor, with the Lock may be implemented wait / notification mode .
  • Compared to object's monitor method
    • It can support multiple waiting queues
    • Support interrupt while waiting
    • Support timeout waiting
  • The creation of the Condition object depends on the Lock object
    • In the source code of Lock, we can see Condition newCondition();the definition of Get Condition
  • The realization of Condition is realized in AQS

Here we need to say that a lock object corresponds to multiple conditions.
The meaning here is equivalent to that for all threads under the same lock (resource), there are different conditional relationships between them, so we need to define conditions separately

Condition use and source code analysis

As mentioned in the previous introduction, Condition is defined in Lock.
Then when we need to use Condition, we need lock对象.newContion()to get it through methods.

Then you can use the condition object.await() method to enter the wait and release the lock at the same time.
Then other objects wake up the waiting threads by calling the condition object.signal() method, and release the lock at the same time.

Realization of Condition

Condition is an interface, and its specific implementation is in AQS, which exists as an internal class of AQS.
The logic here is clearer, because Condition depends on the existence of Lock, and Lock must use AQS, so the arrangement is like this. There must be AQS in the Lock object, and there must be a Condition.

In the AQS source code, we can find the implementation class ConditionObject of Condition:

public class ConditionObject implements Condition, java.io.Serializable 

The Condition interface does not define a queue, but its implementation class does define a waiting queue.

  • This is a one-way queue
  • To maintain it, firstWaiter and lastWaiter nodes are used
  • The node here is a node that reuses the synchronization queue in AQS (so each node has a thread reference)
  • Each Condition object has a waiting queue
  • A lock object can have multiple Condition objects, so a lock object has a synchronization queue and multiple Condition waiting queues
    Insert picture description here

Let's take a look at the process of Condition waiting, waiting queue, and notification:

wait:

  • First of all, it needs to be clear that, like wait(), the threads that can perform waiting and notification are all threads that have acquired the lock (or synchronization state), so there is no synchronization problem here. The lock helps us solve the thread safety problem.
  • When a thread executes the await() method, it releases the lock, and then constructs a new node to join the end of the waiting queue and enter the waiting state.
  • Then it stays in the queue and stays there until someone signals, wakes up the waiting queue, and adds the firstWaiter (node) of the waiting queue to the end of the synchronization queue. Until I also enter the synchronization queue

Notice:

  • Like await(), the thread executing the signal method also acquires the lock
  • It releases the lock, then waits for the firstWaiter of the queue to move to the synchronous queue, and then wakes up the node.

Let's take a look at the source code: the
first is await():

	public final void await() throws InterruptedException {
    
    
            if (Thread.interrupted())
                throw new InterruptedException();
            //当前节点加入等待队列
            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) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

See my comments in the code

	private Node addConditionWaiter() {
    
    
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
    
    
                unlinkCancelledWaiters();
                t = lastWaiter;
            }

            Node node = new Node(Node.CONDITION);

            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

Here is to create a node, the mode of the node is Condition, and then insert it to the end of the waiting queue

	final int fullyRelease(Node node) {
    
    
        try {
    
    
            int savedState = getState();
            if (release(savedState))
                return savedState;
            throw new IllegalMonitorStateException();
        } catch (Throwable t) {
    
    
            node.waitStatus = Node.CANCELLED;
            throw t;
        }
    }

	public final boolean release(int arg) {
    
    
        if (tryRelease(arg)) {
    
    
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

You can see that the synchronization state is released here, and you actually need to call the tryRelease method of AQS to release the synchronization state.

Then call the LockSupport method to block the thread.

Then look at the signal method:

	public final void signal() {
    
    
        if (!isHeldExclusively())
             throw new IllegalMonitorStateException();
         Node first = firstWaiter;
         if (first != null)
             doSignal(first);//这里
      }
	private void doSignal(Node first) {
    
    
         do {
    
    
             if ( (firstWaiter = first.nextWaiter) == null)
                 lastWaiter = null;
             first.nextWaiter = null;
         } while (!transferForSignal(first) &&//看这
                  (first = firstWaiter) != null);
      }
	final boolean transferForSignal(Node node) {
    
    
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
            return false;

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
        Node p = enq(node);//进入同步队列
        int ws = p.waitStatus;
        if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);//唤醒
        return true;
    }

It can be seen here that during signal, the awakened object is firstWaiter.

After the layer-by-layer call, the node is first added to the synchronization queue through the enq function, and then
the firstWaiter thread is awakened using the unpark method of LockSupport.

Guess you like

Origin blog.csdn.net/qq_34687559/article/details/114287156