AQS深入理解 doReleaseShared源码分析 JDK8

前言

Release action for shared mode – signals successor and ensures propagation. (Note: For exclusive mode, release just amounts to calling unparkSuccessor of head if it needs signal.)

对于共享锁的Release动作——唤醒后继并且确保传播。相对于 独占锁来说,相对应的函数就是unparkSuccessor。

JUC框架 系列文章目录

调用doReleaseShared的流程

从文章AQS深入理解 setHeadAndPropagate源码分析 JDK8中可知,想要获取共享锁的线程可能经过acquireShared(int arg) -> doAcquireShared(arg) -> 重复着阻塞和被唤醒(可能是这样) ->setHeadAndPropagate(node, r) -> doReleaseShared(),所以,调用doReleaseShared的线程可能是一个刚获取到共享锁的线程。

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

而调用releaseShared代表某个线程正要释放共享锁,而它也会调用到doReleaseShared。

  • 总之,调用doReleaseShared函数的线程可能有两种:一是 刚获取到共享锁的线程(一定情况下,才调用doReleaseShared);二是 释放共享锁的线程(肯定调用)。
  • 上面这两种情况:1.刚获取到共享锁 2.释放共享锁。都说明 队列中第一个等待中的node,极有可能也能获取共享锁,所以都需要调用doReleaseShared
  • 队列中第一个等待中的node 即 head的后继,所以doReleaseShared中一定会重点照顾它的。

doReleaseShared分析

    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;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }
  • 逻辑是一个死循环,每次循环中重新读取一次head,然后保存在局部变量h中,再配合if(h == head) break;,这样,循环检测到head没有变化时就会退出循环。注意,head变化一定是因为:acquire thread被唤醒,之后它成功获取锁,然后setHead设置了新head。而且注意,只有通过if(h == head) break;即head不变才能退出循环,不然会执行多次循环。
  • if (h != null && h != tail)判断队列是否至少有两个node,如果队列从来没有初始化过(head为null),或者head就是tail,那么中间逻辑直接不走,直接判断head是否变化了。
  • 如果队列中有两个或以上个node,那么检查局部变量h的状态:
    • 如果状态为SIGNAL,说明h的后继是需要被通知的。通过对CAS操作结果取反,将compareAndSetWaitStatus(h, Node.SIGNAL, 0)unparkSuccessor(h)绑定在了一起。说明了只要head成功得从SIGNAL修改为0,那么head的后继的代表线程肯定会被唤醒了。
    • 如果状态为0,说明h的后继所代表的线程已经被唤醒或即将被唤醒,并且这个中间状态即将消失,要么由于acquire thread获取锁失败再次设置head为SIGNAL并再次阻塞,要么由于acquire thread获取锁成功而将自己(head后继)设置为新head并且只要head后继不是队尾,那么新head肯定为SIGNAL。所以设置这种中间状态的head的status为PROPAGATE,让其status又变成负数,这样可能被被唤醒线程检测到。
    • 如果状态为PROPAGATE,直接判断head是否变化。
  • 两个continue保证了进入那两个分支后,只有当CAS操作成功后,才可能去执行if(h == head) break;,才可能退出循环。
  • if(h == head) break;保证了,只要在某个循环的过程中有线程刚获取了锁且设置了新head,就会再次循环。目的当然是为了再次执行unparkSuccessor(h),即唤醒队列中第一个等待的线程。

head状态为0的情况

  • 如果等待队列中只有一个dummy node(它的状态为0),那么head也是tail,且head的状态为0。
  • 等待队列中当前只有一个dummy node(它的状态为0),acquire thread获取锁失败了(无论独占还是共享),将当前线程包装成node放到队列中,此时队列中有两个node,但当前线程还没来得及执行shouldParkAfterFailedAcquire
  • 此时队列中有多个node,有线程刚释放了锁,刚执行了unparkSuccessor里的if (ws < 0) compareAndSetWaitStatus(node, ws, 0);把head的状态设置为了0,然后唤醒head后继线程,head后继线程获取锁成功,直到head后继线程将自己设置为AQS的新head的这段时间里,head的状态为0。

总结:

  • head状态为0的情况,属于一种中间状态。
  • 这种中间状态将变化为,head状态为SIGNAL,不管acquire thread接下来是获取锁成功还是失败。不过获取锁成功这种情况,需要考虑head后继(也就是包装acquire thread的那个node)不是队尾,如果是队尾,那么新head的状态也是为0的了。

这个函数的难点在于,很可能有多个线程同时在同时运行它。比如你创建了一个Semaphore(0),让N个线程执行acquire(),自然这多个线程都会阻塞在acquire()这里,然后你让另一个线程执行release(N)

  • 此时 释放共享锁的线程,肯定在执行doReleaseShared。
  • 由于 上面这个线程的unparkSuccessor,head后继的代表线程也会唤醒,进而执行doReleaseShared。
  • 重复第二步,获取共享锁的线程 又会唤醒 新head后继的代表线程。

观察上面过程,有的线程 因为CAS操作失败,或head变化(主要是因为这个),会一直退不出循环。进而,可能会有多个线程都在运行该函数。

不过由于CAS操作的保证,unparkSuccessor只会在成功设置status从SIGNAL变到0时,才会调用。

特殊情况

PROPAGATE状态设置后,并没有被检测到

在这里插入图片描述
这种情况下,设置的PROPAGATE状态没有被检测到。doReleaseShared的循环将会继续执行。

总结

  • doReleaseShared会尝试唤醒 head后继的代表线程,如果线程已经唤醒,则仅仅设置PROPAGATE状态。
  • 上一条的“尝试唤醒 head后继的代表线程”和“设置PROPAGATE状态”都是CAS操作,如果CAS失败,循环会马上continue并再次尝试。
  • 当检测到局部变量h与当前最新head是不同对象时,说明有acquire thread刚获取了锁,那下一个等待线程也很可能可以获取锁,所以不会break,循环继续。
  • 12
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值