并发系列(九)-----AQS详解共享模式的资源获取与释放

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/MyPersonalSong/article/details/84335431

一 简介

    到目前为止已经知道了AQS中同步队列的基本的工作原理可以总结为维护同步队列,获取资源和改变线程状态。上一篇文章中主要总结了独占模式下的资源获取。这篇主要总结一下AQS中的共享模式。

   共享模式从字面上理解就是,这个资源可以被多个线程共享。在独占模式下,我们知道state的状态最初的值是0.如果某个线程获取到资源了state就加了1释放资源了就减去1。当state变为0的时候唤醒后继节点的线程,让后继节点的线程去持有资源。那么好了我们可以不可以这么干,一开始我给state设定一个值,当一个线程获取资源后,我的state就减去1,其它在来时我在减去1...以此类推,直到线程获取资源减到为0为止。表示资源没有了其他线程就无法获取了只能去等待了。这样的话多个线程就将这个state共享了,其实这就是AQS中的共享模式。

二 共享资源的获取

   上面我们已经总出了它的大体逻辑了,可能在实现中还有一些细节需要去理解。下面看共享资源的获取

    /**
     *共享资源的获取
     *
     * @param arg 要获取的资源数
     */
    public final void acquireShared(int arg) {
        //获取共享锁小于0表示资源没有了,也就获取失败了
        if (tryAcquireShared(arg) < 0) {
            //真正的去获取资源的方法
            doAcquireShared(arg);
        }
    }

 上面的方法中有关tryAcquireShared()我们就不必看了,看的话也是抛了一个异常,但是可以去看看它子类的实现比如Semaphore的实现,如果小于0表示没有获取到资源。看一下没有获取到资源会怎么做,下面是源码。

 /**
     * 共享模式的资源竞争
     *
     * @param arg 资源
     */
    private void doAcquireShared(int arg) {
        //添加一个共享节点到同步队列尾部
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            //是否中断
            boolean interrupted = false;
            for (; ; ) {
                //获取当节点的前驱节点
                final Node p = node.predecessor();
                if (p == head) {
                    //如果前驱节点时头接节点的话,再次获取资源r代表的是剩余资源
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        //大于0获取到了就要检查还有没有资源,有的话还要去继续去唤醒下一个节点
                        setHeadAndPropagate(node, r);
                        p.next = null;
                        if (interrupted) {
                            selfInterrupt();
                        }
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt()) {
                    interrupted = true;
                }
            }
        } finally {
            if (failed) {
                cancelAcquire(node);
            }
        }
    }

上面的方法和独占模式下的整体上没有太大的区别,但是在共享模式下获取到资源后加了一个setHeadAndPropagate()这个方法,这个方法是用来判断资源还有剩余且下一个节点是否是共享节点的。下面是源码

    /**
     * 设置队列头部,并检查后继者是否在等待 在共享模式下,
     *
     * @param node      node节点
     * @param propagate 剩余资源数
     */
    private void setHeadAndPropagate(Node node, int propagate) {
        //获取头节点
        Node h = head;
        //将刚获取到资源的节点设置为头节点
        setHead(node);
        //propagate>0表示还有资源可以获取
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
                (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            //如果当前节点的下一个节点为空,或者下一个节点是共享
            //节点的时
            if (s == null || s.isShared()) {
                //释放资源
                doReleaseShared();
            }
        }
    }

上面这个方法用来将刚获取到资源的节点设置为头节点,同时还检查了有没有资源和有没有共享节点。如果有的话释放资源(注意的释放资源并没有操作state只是做了唤醒下一个节点,由下一个节点的线程去操作资源)下面是源码

/**
     *
     *
     * 唤醒下一个节点
     */
    private void doReleaseShared() {
        for (; ; ) {
            //获取头
            Node h = head;
            //如果头节点不为空,又不是尾节点的话
            if (h != null && h != tail) {
                //获取当前头节点的状态
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    //更新成功了的话唤醒下一个节点
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) {
                        continue;
                    } else {
                        //唤醒下一个节点
                        unparkSuccessor(h);
                    }
                    //将节点状态设置为-3传播状态
                } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) {
                    continue;
                }
            }
            //这里当唤醒线程的时候这里的头节点head是可能变得,一但获取成功就变了,而h还是以前头节点的引用
            if (h == head) {
                break;
            }
        }
    }

这个方法可能有这样一个问题,这个循环不就循环一次吗?为什么要用死循环呢?其实不是的,h是一个头节点的引用,这个h可能是一个旧的头节点,因为在上一个方法中有设置头节点的方法,head是获取资源的线程节点,在成功的唤醒了下一个节点而且被唤醒的节点前驱节点又是头节点,资源有存在且没有中断的话是可以获取成功的获取资源的,那么这个head就有可能是被修改了,所以for循环才会一直继续.。才会继续去拿资源一直拿到没有为止。

三 共享资源的释放

在上面的方法中已经介绍了资源的释放,而且资源的释放就是操作state和唤醒下一个节点,下面是源码的

    /**
     * 释放资源
     * 
     * @param arg 要释放的资源数
     * @return 是否释放成功
     */
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

调用tryReleaseShared(int)由子类实现去操作并state,返回boolean内部调用doReleaseShared()方法。doReleaseShared()方法在上文已经介绍过了。

四 总结

   共享模式的资源获取与释放主要在于将资源预先初始化一定数目,然后各个线程去获取特定数量的资源,在获取资源时如果资源还有剩下就唤醒后继节点继续获取,知道资源没有剩余,其他资源只能进入等待状态。释放时做了操作资源和唤醒下一个节点的操作。

猜你喜欢

转载自blog.csdn.net/MyPersonalSong/article/details/84335431
今日推荐