Condition条件队列源码解析2.await()与signal()解析

Condition入门介绍

流程图

await()流程图

在这里插入图片描述

await()节点出队重新构造入队流程图

在这里插入图片描述

signal流程图

在这里插入图片描述

signal节点迁移示意图

在这里插入图片描述

1.Condition接口

   public interface Condition {
    
    
	
    //阻塞 可以响应中断。
    void await() throws InterruptedException;

	//阻塞 不响应中断
    void awaitUninterruptibly();
	
    //可以指定挂起时间
    long awaitNanos(long nanosTimeout) throws InterruptedException;

    boolean await(long time, TimeUnit unit) throws InterruptedException;
	
    boolean awaitUntil(Date deadline) throws InterruptedException;
	
    //唤醒条件队列中第一个可以被唤醒的节点(将等待队列的节点迁移到同步队列中)
    void signal();
	
    //唤醒条件队列中的所有节点全部迁移到同步队列中
    void signalAll();
}

2.实现类ConditionObject

   /*
	* 此类是AQS中的一个内部类,等待队列使用的节点也是AQS的内部类 Node.
	*  只不过与AQS的队列不同的是,等待队列中是单向链表。AQS的队列是双向链表。
	*  注意 普通内部类是可以访问到类外部的域,(后面要做节点的迁移)
    */
public class ConditionObject implements Condition, java.io.Serializable {
    
    
		       
    	//条件队列的头节点
        private transient Node firstWaiter;
        
    	//条件队列的尾节点
        private transient Node lastWaiter;
    	
    	// lock.newCondition() 最终调用的还是这个构造器
   		public ConditionObject() {
    
     }

3.await()方法

下面简称AQS的队列为同步队列,Condition的队列(单向链表)下面简称为等待队列

        public final void await() throws InterruptedException {
    
    
            
            //判断当前线程是否是中断状态,如果是则直接抛出中断异常。。
            if (Thread.interrupted())
                throw new InterruptedException();
            
            /*
             * addConditionWaiter()将当前线程封装成Node节点加入条件队列中
             * 并返回封装完成的Node节点  (类似AQS中的addWaiter方法)
             */
            Node node = addConditionWaiter();
            
            /*
             *  fullyRelease() 完全释放当前线程的锁(state)
             *  因为这里要挂起等待被唤醒,所以必须先完全释放锁。 
             */
            long savedState = fullyRelease(node);
            
            
            /*
             *  0 表示在Condition 队列挂起期间没有接收过中断信号
             *  -1 在Condition队列挂起期间接收到了中断信号 (THROW_IE)
             *  1 在Condition队列挂起期间接未收到了中断信号,但是迁移到"阻塞(同步)队
             *   列AQS的队列"之后,接收过中断信号 (REINTERRUPT)
             */
            int interruptMode = 0;
            
            
            /*
             *  isOnSyncQueue(node) 
             * true表示当前节点已经迁移到了同步队列中
             * false表示当前节点还在等待队列中如果当前节点还在等待队列中,则需要继续挂起。
             */
            while (!isOnSyncQueue(node)) {
    
    
                //当前节点还在等待队列中,继续被挂起。
                LockSupport.park(this);
                
                /*
                 * 这里就是唤醒后判断是否是被中断唤醒的,然后执行响应的中断逻辑。
                 * 
                 * checkInterruptWhileWaiting()就算在等待队列挂起期间,
                 * 线程发生了中断,对应的node也会被迁移到同步队列(参考transferAfterCancelledWait)
                 * 如果当前node被中断过,也直接break。
                 * 
                 *  什么时候会被唤醒?
                 *  1.常规:外部线程获取锁后,调用了signal()方法,转移条件队列的头节点,
                 *    到同步队列,当这个节点获取锁后,会被唤醒
                 *  2.转移到同步队列后,发现同步队列的前驱节点状态时取消状态,此时会直接唤醒当前节点
                 *  3.当前节点挂起期间,被外部线程使用中断唤醒。。
                 */
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            
            /*
             * 到这里 说明当前node已经被迁移到了同步队列中
             */
            
            /*
             * 1. acquireQueued()就是在同步队列中竞争锁的逻辑
             *   返回true -> 表示在同步队列中被外部线程中断唤醒过 
             *   返回false -> 表示在同步队列中没有被外部线程中断唤醒过
             * 2. 条件二成立表示没有在等待队列发生过中断
             *  
             *   两个条件同时成立,表示node在同步队列发生过中断。
             *   所以将interruptMode设置为REINTERRUPT(在同步队列发生过中断)
             */
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            
            //考虑下 node.nextWaiter != null, 条件什么时候成立呢?
            //其实是node在条件队列内时如果被外部线程中断唤醒时,会加入到阻塞队列
            //但是并为设置nextWaiter = null. 这里需要做一个清理工作。
            if (node.nextWaiter != null) 
                //清理等待队列内取消状态的节点。
                unlinkCancelledWaiters();
             	
            /*
             *  条件成立:说明挂起期间发生过中断 1.条件队列的挂起 2.条件队列之外的挂起
             */
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

3.1addConditionWaiter

         /*
          *  将当前线程封装成一个Node加入到等待队列的末尾。
          *   (会将等待队列中所有处于取消状态的节点全部出队)
          */
		private Node addConditionWaiter() {
    
    
            //指向队尾
            Node t = lastWaiter;
            
            /*
             *  队列不为空 并且 队尾的节点状态不是Condition(-2)而处于取消状态(cancel)
             *  此时当前线程在队列中做一次清除处理,将所有处于取消状态的node全部干掉。
             */ 
            if (t != null && t.waitStatus != Node.CONDITION) {
    
    
                unlinkCancelledWaiters();
                //最终t指向的还是尾结点(t可能会更新)
                t = lastWaiter;
            }
            
            //将当前线程封装为一个Node,Node状态(waitStatus)为Condition(-2)
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            
            //下面就是入队的过程 (这里只有加锁的线程可以进来,所以不需要加锁)
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            //最终返回构造的节点
            return node;
        }

3.2fullyRelease()

    final long fullyRelease(Node node) {
    
    
        boolean failed = true;
        try {
    
    
            //获取当前的state的值
            long savedState = getState();
  
            /*
             * 全部释放掉state,变为初始状态0 这里正常情况下都会成功。 
             * (release方法见AQS源码解析系列博客)
             */
            if (release(savedState)) {
    
    
                failed = false;
                
                /*
                 *  这里最终要返回加锁前的状态,因为执行到这时,构造出来的Node
                 *  节点还在等待队列中,当迁移到同步队列时还需要进行获取锁释放锁的逻辑
                 *   所以这里需要记录下初始锁的状态并返回。
                 */  
                return savedState;
            } else {
    
    
                throw new IllegalMonitorStateException();
            }
        } finally {
    
    
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

3.3isOnSyncQueue()

    /*
     *  判断当前Node是否在同步队列中
     */
	final boolean isOnSyncQueue(Node node) {
    
    
        /*
         * 条件一: node.waitStatus == Node.Condition (-2)条件成立,说明当前Node
         * 一定在等待队列,因为signal方法迁移节点到同步队列前,会将node的状态设置为0
         *  
         *  条件二: 前置条件 node.waitStatus != Node.Condition 
         *     1.此时node.waitStatus 可能= 0 (表示当前节点已经被signal了)
         *     2.此时node.waitStatus 可能= 1(取消) (当前线程为持有锁调用await方法,最终会将node的状态改为取消状态)
         *  
         *   node.waitStatuas == 0 为什么还要判断 node.prev == null?
         *   因为signal是先修改状态 在迁移
         *   因为等待队列的Node是单向链表,node.prev==null说明一定是在等待队列中
         */
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            //直接return false。
            return false;
        
        /*
         *  执行到这里,会使哪种情况?
         *  node.waitStatus != Condition 且 node.prev != null 
         *  可以排除 node.waitStatus == 1取消状态,
         *  为什么可以排除? 因为signal不会把取消状态的node迁移走
         *  
         *  设置prev引用的逻辑 是 迁移到同步队列设置的(enq()自旋入队)
         *  入队的逻辑:
         *  1.设置node.prev = tail; 
         *  2.CAS设置当前node为tail,成功才算正在进入到同步队列
         *  3.pred.next = node;
         *  就算prev不为null,也不能推算出当前node已经成功入队到同步队列了,(CAS成功才算)
         */
        
        
 		 /*
          * next不为null,说明当前节点已经成功入队到同步队列了,且当前节点后面已经有其他node了。。。 
          */
        if (node.next != null) 
            return true;
        
        /*
         * 执行到这里,说明当前节点的状态为 node.prev != null && node.witStatus = 0
         * 然后从同步队列中遍历查找node,成功返回ture,失败返回false。
         *  
         *  当前node有可能在signal过程中,正在迁移中。。还未完成
         */
        return findNodeFromTail(node);
    }

3.4.checkInterruptWhileWaiting()

    private int checkInterruptWhileWaiting(Node node) {
    
    
        	/*
        	 *  检查线程是否被中断?
        	 *    被中断过就去判断是在那个队列中被中断的。最终返回-1 / 1
        	 *    没有被中断过直接返回0.
        	 */
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
    }

3.5.transferAfterCancelledWait()

      /*
       *  @return true 表示node是在
       *		  false 表示node是在
       */
	  final boolean transferAfterCancelledWait(Node node) {
    
    
        
        /*
         *  条件成立: 说明当前的node一定是在等待队列中被中断唤醒的,因为
         *  signal迁移节点到同步队列时,会将节点的状态修改为0
         */
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
    
    
            //在等待队列中中断唤醒的node也会被迁移到同步队列中
            enq(node);
            //直接return true。
            return true;
        }
		
        /*
         *  执行到这里有几种情况?
         *   1.当前node已经被外部线程调用signal 方法将其迁移到 同步队列了
         *   2.当前node正在被外部线程调用signal 方法将其迁移到同步队列 进行中。。。
         *   (因为将node从等待队列迁移到同步队列时会先将状态改为0)
         */
        while (!isOnSyncQueue(node))
            Thread.yield();
        //这里表示node是在同步队列中被中断的。
        return false;
    }

3.6unlinkCancelledWaiters

    /*
     *  将等待队列中的所有取消状态的node全部出队。 
     */
	private void unlinkCancelledWaiters() {
    
    
        	//循环当前节点
            Node t = firstWaiter;
            //当前链表上一个正常状态的node节点
            Node trail = null;
            //遍历
            while (t != null) {
    
    
                Node next = t.nextWaiter;
                //当前节点t状态不正常
                if (t.waitStatus != Node.CONDITION) {
    
    
                    //直接
                    t.nextWaiter = null;
                    if (trail == null)
                        firstWaiter = next;
                    else //直接将t删除 
                        trail.nextWaiter = next;
                    if (next == null)
                        lastWaiter = trail;
                }
                else
                    trail = t;
                t = next;
            }
        }

3.7reportInterruptAfterWait

private void reportInterruptAfterWait(int interruptMode)
    throws InterruptedException {
    
    
    //条件成立: 说明在条件队列内发生过中断,此时await方法抛出中断异常
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    //条件成立:说明在条件队列外发生的中断,此时设置当前线程的中断标记为为 true
    //中断处理 交给业务
    else if (interruptMode == REINTERRUPT)
        selfInterrupt();
}

4.signal()唤醒后的逻辑

        public final void signal() {
    
    
            //判断当前线程是否是持有锁的线程
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            
            //获取条件队列的第一个node
            Node first = firstWaiter;
            
            //不为NULL,则将第一个节点进行迁移到同步队列的逻辑
            if (first != null)
                doSignal(first);
        }

4.1doSignal

        private void doSignal(Node first) {
    
    
            //自旋
            do {
    
    
                /*
                 *  当前节点要进行出队,所以更新firstWaiter的指向
                 *  如果当前等待队列只有一个节点的话,也更新lastWaiter。
                 */
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null; 
                first.nextWaiter = null;
                
                /*
                 *   transferForSignal(first) true->迁移成功 false->迁移失败
                 *   这里表示要么迁移一个节点成功 要么 队列为空跳出循环。
                 */         
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

4.2AQS.transferForSignal

    /*
     *  @return true表示成功迁移 false表示没有成功迁移
     */
	final boolean transferForSignal(Node node) {
    
    
        /*
         *  CAS修改的当前Node的状态为0,因为马上node要迁移到同步队列了,
         *  如果CAS失败,说明当前Node已经被取消了,直接返回false即可。
         *  (线程await时,未持有锁,最终线程对应的node会设置为取消状态)
         *  (node对应的线程,挂起期间,被其他线程使用中断信号唤醒过。。会主动进入同步队列
         *   并且将状态设置为0)
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        
        /*
         * enq() 自旋进入同步队列,一定会入队成功,
         * 最终返回当前node进入同步队列后的前驱节点
         * 
         * enq()方法参考AQS源码解析
         */
        Node p = enq(node);
        
        //获取前驱节点的waitStatus。
        int ws = p.waitStatus;
        
        /*
         *  1.判断如果前驱节点的状态 > 0(取消),那么直接唤醒node。
         *  2.前驱节点的node <= 0,那么尝试CAS修改为-1(signal),即前驱节点释放锁后
         *    唤醒后继节点。
         *   设置失败说明前驱节点的状态突然被取消了,需要唤醒后继节点。
         *   当前驱node对应的线程是lockInterrupt入队时的node,是会响应中断的,外部
         *   线程给前驱node中断信号之后,前驱node会将状态修改为取消状态,并且执行出队逻辑
         */
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            
            //走到这里 说明前驱节点状态处于取消状态,那么直接唤醒node。
            LockSupport.unpark(node.thread);
        return true;
    }

猜你喜欢

转载自blog.csdn.net/qq_46312987/article/details/121740083