JUC-(11)AQS(中)-共享模式

对比

独占模式 共享模式
acquire(int arg) acquireShared(int arg)
acquireInterruptibly(int arg) acquireSharedInterruptibly(int arg)
tryAcquireNanos(int arg, long nanosTimeout) tryAcquireSharedNanos(int arg, long nanosTimeout)
release(int arg) releaseShared(int arg)

上面列出了独占模式和共享模式获取锁和释放锁的入口方法.我们对比分析就能很清楚的了解它们之间的不同.

共享模式获取锁

同独占模式一样,获取锁的入口方法我们从acquireShared(int arg)开始,另外还有两个入口如果有兴趣自己分析即可.

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

上面代码第一步开始调用tryAcquireShared(arg)尝试获取共享资源,而在独占模式中是调用tryAcquire(arg).同独占模式一样这个方法也需要同步器自己去实现.这个方法返回值为剩余资源的个数,主要可以分为三种情况:

  • 0:获取共享资源成功,但是没有剩余的资源了.
  • >0:获取资源成功,还有剩余资源
  • <0:获取资源失败.

如果获取资源成功则直接返回了,如果失败了则进入doAcquireShared(arg),而独占模式下则是进入acquireQueued(addWaiter(Node.EXCLUSIVE), 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) {
                    //如果自己是老二就有资格可以尝试获取资源
                    int r = tryAcquireShared(arg);
                    //如果资源还有剩余
                    if (r >= 0) {
                        //将自己设置为头节点,并传播(后面会解释传播)
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                //尝试获取资源失败,后续操作同独占锁
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

上面的代码与独占模式下整体上并没有太大差别,主要流程如下:

  1. 调用addWaiter将自己添加到队列的尾部
  2. 获取当前节点的前节点,然后看前节点是不是头节点.
  3. 如果当前节点的前节点是头节点,自己就有资格去尝试获取资源.如果获取失败了就进行后面操作逻辑.如果尝试获取资源成功了,则会调用setHeadAndPropagate(node, r)将自己设置成头节点并往后传播.关于setHeadAndPropagate这个方法后面会细讲.
  4. 第2步判断当前节点不是老二或者第3步获取资源失败就会进入这个逻辑.这个逻辑主要做两件事:将前置节点设置waitStatuss设置成-1让当前线程挂起.这个逻辑与独占锁中的并没有差别.

在第3步中调用tryAcquireShared获取到资源后的操作与独占模式中不一样.在独占模式中是调用setHead将自己设置成头节点,具体代码如下:

    //独占模式只会将当前节点设置成头节点
    private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

而在共享模式中,它会调用setHeadAndPropagate不仅仅只是将当前节点设置成头节点,还有将当前节点的后继几点继续唤醒,具体代码如下:

    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head;
        setHead(node);
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                //唤醒等待获取共享资源的线程,后面细说.目前知道这个方法就是去唤醒等待的节点
                doReleaseShared();
        }
    }

收先要明确为什么能进入这个方法,这个方法中的node一定是代表它是当前成功获取了共享资源的线程,propagate代表了当前线程获取了共享资源后还剩余的线程数.这个方法一定是当前线程成功获取了共享资源才会进来的.首先它通过一个h用来保存旧的head节点,然后将自己设置成head节点.然后进入后面这一堆的判断.下面我们来分析这一系列的判断

  • propagate > 0:当前线程获取资源后发现还有剩余资源,那么这个时候就需要唤醒等待的节点.这个很好理解,因为是共享模式,当资源有多余的时候就唤醒其他等待资源的线程.要不然怎么叫共享呢?
  • h.waitStatus < 0:这个代表老的head节点后面的节点可以被唤醒.
  • (h = head) == null || h.waitStatus < 0:这个代表新的head节点后面的节点需要被唤醒.

总结来说就是两点:当propagate > 0时说明资源可用所以唤醒节点,而h.waitStatus < 0说明不管是新的头结点还是老的头节点只要它的waitStatus < 0都需要唤醒节点.

关于h == null这个条件我觉得不会生效.因为进入该方法前addWaiter已经调用了.CLH队列中至少会存在一个节点所以我觉得这个条件不会生效.目前我也想不出什么情况下h == null.

上面就是整个共享模式下获取资源的整个过程.整体上与独占模式下差别不太大.都是获取到了直接返回执行业务代码,获取失败则进入阻塞.但是最大的不同点在于:共享模式下获取共享资源成功的情况下同时还会去唤醒等待的线程,而在独占模式下是不会的.

共享模式下释放锁

共享锁的释放的方法入口是releaseShared,它的源代码如下:

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

该方法的实现比较简单,tryReleaseShared(arg)(arg)这个是同步器需要自己实现的方法.释放锁时最核心的方法就是doReleaseShared().该方法在之前获取共享资源时调用过,现在在释放锁的时候也调用过.我们来看该方法的实现:

private void doReleaseShared() {
        for (;;) {
            //使用h保存久的头节点
            Node h = head;
            //说明队列种存在两个以上的节点
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    //ws = -1 说明需要唤醒后续节点,将h节点设置为0
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;
                    //在独占模式已经分析过了
                    unparkSuccessor(h);
                }
                //可能该节点被其他线程修改成0
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;
            }
            //说明上面的条件都没有满足的,所以退出
            if (h == head)
                break;
        }
    }

上面的代码主要就是设置头节点状态为0,然后唤醒后续的节点.其中关于PROPAGATE状态的引入可以参照PROPAGATE状态存在的意义.

小结

上面内容基于独占模式的对比做了共享模式下锁的获取和释放.整体流程和独占模式下大致相同,最大的不同点就在于共享模式成功获取资源后还可能会唤醒后续等待的线程,而独占模式是不会这样做的.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355