java并发之AQS共享模式源码解读

43 篇文章 0 订阅

                              java并发之AQS共享模式源码解读

上一篇文章简单解读了AQS的源码以及独占模式下获取锁的流程以及细节,碍于篇幅,这篇文章来说说AQS的共享模式。

共享锁的一个实现类就是信号量Semaphore,Semaphores一般用于对某种访问资源的限制,一个信号量相当于持有一些许可(permits),线程可以调用Semaphore对象的acquire()方法获取一个许可,调用release()来归还一个许可。

同样Semaphore有公平和非公平两种模式。默认就是非公平模式。

先看下AQS的acquireShared()方法

    /**
     * Acquires in shared mode, ignoring interrupts.  Implemented by
     * first invoking at least once {@link #tryAcquireShared},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquireShared} until success.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquireShared} but is otherwise uninterpreted
     *        and can represent anything you like.
     */
    public final void acquireShared(int arg) {
        /**
        tryAcquireShared()方法由自定义同步器实现,
        */
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
   /**
     * Acquires in shared uninterruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);//构造shared node加入队列
        boolean failed = true;//是否成功
        try {
            boolean interrupted = false;//是否被中断
            for (;;) {//自旋等待
                final Node p = node.predecessor();//获取前驱结点
                if (p == head) {//前驱结点是head的情况下
                    int r = tryAcquireShared(arg);//负数表示获取失败 0 表示获取成功,没有剩余资源 正数表示获取成功,还有剩余资源
                    if (r >= 0) {//如果还有剩余资源,通知后面等等的共享节点
                        setHeadAndPropagate(node, r);//设置head结点,并且通知后继等待的共享结点
                        p.next = null; // help GC 帮助回收
                        if (interrupted)//如果中断
                            selfInterrupt();//响应中断
                        failed = false;
                        return;
                    }
                }
// 判断状态,寻找安全点,进入waiting状态,等着被unpark()或interrupt()
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

    /**
     * Sets head of queue, and checks if successor may be waiting
     * in shared mode, if so propagating if either propagate > 0 or
     * PROPAGATE status was set.
     *
     * @param node the node
     * @param propagate the return value from a tryAcquireShared
     */
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        /*
         * Try to signal next queued node if:
         *   Propagation was indicated by caller,
         *     or was recorded (as h.waitStatus) by a previous operation
         *     (note: this uses sign-check of waitStatus because
         *      PROPAGATE status may transition to SIGNAL.)
         * and
         *   The next node is waiting in shared mode,
         *     or we don't know, because it appears null
         *
         * The conservatism in both of these checks may cause
         * unnecessary wake-ups, but only when there are multiple
         * racing acquires/releases, so most need signals now or soon
         * anyway.
         */
     //这里意思有两种情况是需要执行唤醒操作
    //1.propagate > 0 表示调用方指明了后继节点需要被唤醒,其实就是还有剩余资源的意思
    //2.头节点后面的节点需要被唤醒(waitStatus<0),不论是老的头结点还是新的头结点
        if (propagate > 0 || h == null || h.waitStatus < 0) {
            Node s = node.next;
            //如果后继节点为空,或者后继节点是共享模式的节点 则唤醒
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

acquire()总结

  1. tryAcquireShared()尝试获取资源,成功则直接返回,否则进入流程2。
  2. doAcquireShared()会将当前线程加入等待队列尾部休息,直到其他线程释放资源唤醒自己。它还会尝试着让唤醒传递到后面的节点。

其实跟acquire()的流程大同小异,只不过多了个自己拿到资源后,还会去唤醒后继队友的操作(这才是共享嘛)

接下来看下releaseShared()

    /**
     * Releases in shared mode.  Implemented by unblocking one or more
     * threads if {@link #tryReleaseShared} returns true.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryReleaseShared} but is otherwise uninterpreted
     *        and can represent anything you like.
     * @return the value returned from {@link #tryReleaseShared}
     */
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

    /**
     * Release action for shared mode -- signal successor and ensure
     * propagation. (Note: For exclusive mode, release just amounts
     * to calling unparkSuccessor of head if it needs signal.)
     */
   private void doReleaseShared() {
    /*
     * 如果head需要通知下一个节点,调用unparkSuccessor
     * 如果不需要通知,需要在释放后把waitStatus改为PROPAGATE来继续传播
     * 此外,我们必须通过自旋来CAS以防止操作时有新节点加入
     * 另外,不同于其他unparkSuccessor的用途,我们需要知道CAS设置状态失败的情况,
     * 以便进行重新检查。
     */
    for (;;) {
        //唤醒操作由头结点开始,注意这里的头节点已经是上面新设置的头结点了
        //其实就是唤醒上面新获取到共享锁的节点的后继节点
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            //表示后继节点需要被唤醒
            if (ws == Node.SIGNAL) {
                //这里需要控制并发,因为入口有setHeadAndPropagate跟release两个,避免两次unpark
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;           
                //执行唤醒操作
                unparkSuccessor(h);
            }
            //如果后继节点暂时不需要唤醒,则把当前节点状态设置为PROPAGATE确保以后可以传递下去
            else if (ws == 0 &&
                    !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;               
        }
        //如果头结点没有发生变化,表示设置完成,退出循环
        //如果头结点发生变化,比如说其他线程获取到了锁,为了使自己的唤醒动作可以传递,必须进行重试
        if (h == head)                   
            break;
    }
}

到这里,AQS的解读先告一段落。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值