AQS_共享模式_CountDownLatch——await,countDown

await

入队效果

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

acquireSharedInterruptibly

        /**
         *  共享式的获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,
         *  在同一时刻可以有多个线程获取到同步状态,该方法可以响应中断
         */
        //AQS
        public final void acquireSharedInterruptibly(int arg)
                throws InterruptedException {
            //条件成立:说明当前调用await方法的线程 已经是 中断状态了,直接抛出异常..
            if (Thread.interrupted())
                throw new InterruptedException();
            //条件成立:说明当前AQS.state > 0 ,此时将线程入队,然后等待唤醒..
            //条件不成立:AQS.state == 0,此时就不会阻塞线程了..
            //对应业务层面 执行任务的线程已经将latch打破了。然后其他再调用latch.await的线程,就不会在这里阻塞了
            if (tryAcquireShared(arg) < 0)
                doAcquireSharedInterruptibly(arg);
        }

doAcquireSharedInterruptibly

        private void doAcquireSharedInterruptibly(int arg)
                throws InterruptedException {
            //将调用latch.await()方法的线程 包装成node加入到 AQS的阻塞队列当中。
            final AbstractQueuedSynchronizer.Node node = addWaiter(AbstractQueuedSynchronizer.Node.SHARED);
            boolean failed = true;
            try {
                for (;;) {
                    //获取当前线程节点的前驱节点
                    final Node p = node.predecessor();
                    //条件成立,说明当前线程对应的节点 为 head.next节点
                    if (p == head) {
                        //head.next就有权力获取共享锁
                        int r = tryAcquireShared(arg);
                        if (r >= 0) {
                            //设置当前节点为 head节点,并且向后传播!(依次唤醒!)
                            setHeadAndPropagate(node, r);
                            p.next = null; // help GC
                            failed = false;
                            return;
                        }
                    }
                    //shouldParkAfterFailedAcquire  会给当前线程找一个好爸爸,最终给爸爸节点设置状态为 signal(-1),返回true
                    //parkAndCheckInterrupt 挂起当前节点对应的线程...
                    if (shouldParkAfterFailedAcquire(p, node) &&
                            parkAndCheckInterrupt())
                        throw new InterruptedException();
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
/**
 * 设置当前节点为 head节点,并且向后传播!(依次唤醒!)
 */
//AQS.setHeadAndPropagate
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    //将当前节点设置为 新的 head节点。
    setHead(node);

    //调用setHeadAndPropagete 时  propagate  == 1 一定成立
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
        //获取当前节点的后继节点..
        Node s = node.next;
        //条件一:s == null  什么时候成立呢?  当前node节点已经是 tail了,条件一会成立。 doReleaseShared() 里面会处理这种情况..
        //条件二:前置条件,s != null , 要求s节点的模式必须是 共享模式。 latch.await() -> addWaiter(Node.SHARED)
        if (s == null || s.isShared())
            //基本上所有情况都会执行到 doReleasseShared() 方法。
            doReleaseShared();
    }
}

countDown

唤醒效果

public void countDown() {
    sync.releaseShared(1);
}
//AQS.releaseShared
public final boolean releaseShared(int arg) {
    //条件成立:说明当前调用latch.countDown() 方法线程 正好是 state - 1 == 0 的这个线程,需要做触发唤醒 await状态的线程。
    if (tryReleaseShared(arg)) {
        //调用countDown() 方法的线程 只有一个线程会进入到这个 if块 里面,去调用 doReleaseShared() 唤醒 阻塞状态的线程的逻辑。
        doReleaseShared();
        return true;
    }
    return false;
}

tryReleaseShared

/**
 * 更新 AQS.state 值,每调用一次,state值减一,当state -1 正好为0时,返回true
 */
protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
        //获取当前AQS.state
        int c = getState();
        //条件成立:说明前面已经有线程 触发 唤醒操作了,这里返回false
        if (c == 0)
            return false;

        //执行到这里,说明 state > 0

        int nextc = c-1;

        //cas成功,说明当前线程执行 tryReleaseShared 方法 c-1之前,没有其它线程 修改过 state。
        if (compareAndSetState(c, nextc))
            //nextc == 0 :true ,说明当前调用 countDown() 方法的线程 就是需要触发 唤醒操作的线程.
            return nextc == 0;
    }
}
        /**
         * 都有哪几种路径会调用到doReleaseShared方法呢?
         * 1.latch.countDown() -> AQS.state == 0 -> doReleaseShared() 唤醒当前阻塞队列内的 head.next 对应的线程。
         * 2.被唤醒的线程 -> doAcquireSharedInterruptibly parkAndCheckInterrupt() 唤醒 -> setHeadAndPropagate() -> doReleaseShared()
         */
        private void doReleaseShared() {
            /*
             * Ensure that a release propagates, even if there are other
             * in-progress acquires/releases.  This proceeds in the usual
             * way of trying to unparkSuccessor of head if it needs
             * signal. But if it does not, status is set to PROPAGATE to
             * ensure that upon release, propagation continues.
             * Additionally, we must loop in case a new node is added
             * while we are doing this. Also, unlike other uses of
             * unparkSuccessor, we need to know if CAS to reset status
             * fails, if so rechecking.
             */
            for (;;) {
                Node h = head;
                //条件一:h != null 成立,说明阻塞队列不为空..
                //不成立:h == null 什么时候会是这样呢?
                //latch创建出来后,没有任何线程调用过 await() 方法之前,有线程调用latch.countDown()操作 且触发了 唤醒阻塞节点的逻辑..

                //条件二:h != tail 成立,说明当前阻塞队列内,除了head节点以外  还有其他节点。
                //h == tail  -> head 和 tail 指向的是同一个node对象。 什么时候会有这种情况呢?
                //1. 正常唤醒情况下,依次获取到 共享锁,当前线程执行到这里时 (这个线程就是 tail 节点。)
                //2. 第一个调用await()方法的线程 与 调用countDown()且触发唤醒阻塞节点的线程 出现并发了..
                //   因为await()线程是第一个调用 latch.await()的线程,此时队列内什么也没有,它需要补充创建一个Head节点,然后再次自旋时入队
                //   在await()线程入队完成之前,假设当前队列内 只有 刚刚补充创建的空元素 head 。
                //   同期,外部有一个调用countDown()的线程,将state 值从1,修改为0了,那么这个线程需要做 唤醒 阻塞队列内元素的逻辑..
                //   注意:调用await()的线程 因为完全入队完成之后,再次回到上层方法 doAcquireSharedInterruptibly 会进入到自旋中,
                //   获取当前元素的前驱,判断自己是head.next, 所以接下来该线程又会将自己设置为 head,然后该线程就从await()方法返回了...
                if (h != null && h != tail) {
                    //执行到if里面,说明当前head 一定有 后继节点!
                    int ws = h.waitStatus;
                    //当前head状态 为 signal 说明 后继节点并没有被唤醒过呢...
                    if (ws == Node.SIGNAL) {
                        //唤醒后继节点前 将head节点的状态改为 0
                        //这里为什么,使用CAS呢?
                        //当doReleaseShared方法 存在多个线程 唤醒 head.next 逻辑时,
                        //CAS 可能会失败...
                        //案例:
                        //t3 线程在if(h == head) 返回false时,t3 会继续自旋. 参与到 唤醒下一个head.next的逻辑..
                        //t3 此时执行到 CAS WaitStatus(h,Node.SIGNAL, 0) 成功.. t4 在t3修改成功之前,也进入到 if (ws == Node.SIGNAL) 里面了,
                        //但是t4 修改 CAS WaitStatus(h,Node.SIGNAL, 0) 会失败,因为 t3 改过了...
                        if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                            continue;            // loop to recheck cases
                        //唤醒后继节点
                        unparkSuccessor(h);
                    }
                    else if (ws == 0 &&
                            !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                        continue;                // loop on failed CAS
                }
                //条件成立:
                //1.说明刚刚唤醒的 后继节点,还没执行到 setHeadAndPropagate方法里面的 设置当前唤醒节点为head的逻辑。
                //这个时候,当前线程 直接跳出去...结束了..
                //此时用不用担心,唤醒逻辑 在这里断掉呢?、
                //不需要担心,因为被唤醒的线程 早晚会执行到doReleaseShared方法。

                //2.h == null  latch创建出来后,没有任何线程调用过 await() 方法之前,
                //有线程调用latch.countDown()操作 且触发了 唤醒阻塞节点的逻辑..
                //3.h == tail  -> head 和 tail 指向的是同一个node对象

                //条件不成立:
                //被唤醒的节点 非常积极,直接将自己设置为了新的head,此时 唤醒它的节点(前驱),执行h == head 条件会不成立..
                //此时 head节点的前驱,不会跳出 doReleaseShared 方法,会继续唤醒 新head 节点的后继...
                if (h == head)                   // loop if head changed
                    break;
            }
        }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值