用火车购票的方式打开 AQS同步器(二)

1 引言

上篇文章讲述了java AQS结构以及其中排他API的 实现逻辑。而这一篇我们来看看其共享逻辑, 这里依旧使用上文的火车买票为例,便于理解。这里直接会从代码实现讲起,对于还不了解AQS的结构的建议先看一下上一篇文章:用火车购票的方式打开 AQS同步器(一)

2 AQS共享和排他逻辑的区别

共享实现方式与排他逻辑非常类似(毕竟是框架嘛)。排他逻辑其实可以看作是共享逻辑的一种特例,由于它的独占特性。排他逻辑tryAcquire方法返回值为真假,表示是否资源(一票一车的情况)已经被其他人占用。而共享逻辑tryAcquireShared方法返回值为资源数,只有当资源数<0,才表示当前无法获取执行权,它更像现实生活中的买票行为。而在等待状态时两者也表现出一些不同,代码里会讲到。


image.png

3 AQS同步器共享逻辑代码实现

共享(资源共享,只要买了车票后,就都可以上这辆车):

## 获取车票,没有则进入等待
public final void acquireShared(int arg) {        
    if (tryAcquireShared(arg) < 0) // 尝试购票,只要票数 > 0,就可以成功上车           
         doAcquireShared(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) { // 有>1张车票
                        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);
        }
    }
 ## 将自己排第一位,并在车票多,并在状态<0就尝试通知后面的人
 private void setHeadAndPropagate(Node node, int propagate) { // node : 新来候补的人,pro..:车票数
        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 either before
         *     or after setHead) 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.
         */
        if (propagate > 0 || h == null || h.waitStatus < 0 ||  
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared(); // 继续唤醒
        }
    }
## 这里要说明一下 if中的条件:
就如同注释所说,这里可能会引起不必要的唤醒。
propagate 指的是资源数,很好理解>0张后面的人自然可以准备候补。
后面两个h.waitStatus < 0:
前者为旧head的状态。小于0(PROPAGATE,在releaseShare方法(并发调用)中有可能设置为该值),说明有多余的车票出现了
后者为新head < 0,那么其实无论是PROPAGATE 还是SINGAL状态,其后一位都可以尝试后不了
这里可以这么理解:既然有可能马上轮到我,或者车票可能有多余的话,我就可以通知这些人进行尝试候补了​,这些人获取车票的机会很大

释放资源(其中有人退票或者下车了,就又有空余的车票了):

 public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) { // 下车或者退票(用于子类重写)
            doReleaseShared(); // 通知还在排队的人准备候补
            return true;
        }
        return false;
    }

## 需要通知后面的人候补,或者标记可能多张车票释放了
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,                                                                                                                                                                                                                                           */
        for (;;) {
            Node h = head; 
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {  // 唤醒状态,表明后面排队的需要被唤醒了
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h); // 唤醒后面排队的人
                }
                else if (ws == 0 &&  // 即上一段compareAndSetWaitStatus(h, Node.SIGNAL, 0),然后其他人又被调用了该方法
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) // 将其状态设置为可传播,使其后续(被唤醒的人再获取车票失败后,可以再尝试获取一次,详情见shouldParkAfterFailedAcquire方法)
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

对于doReleaseShared简单说明下,为什么有compareAndSetWaitStatus(h, 0, Node.PROPAGATE)这个逻辑呢?因为共享模式下,可能会有多个人同时退票,这时候,按照候补队列FIFO的原则,那只能通知第一个人可以去候补了,但是这个候补的人可能没获取票(可能被特殊人先获取车票)。由于之前是多个人同时退票的(在候补失败后,这个时候又有人退票了),那么对于这次没有获取到车票的人,再获取一次车票是很有可能会成功的。所以说Node.PROPAGATE这个状态表示就像是,你在尝试候补失败的过程中又有人退票了,所以你再发起一次候补,成功机会 很大。

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