【Java并发编程】AQS(3)——独占锁的释放

今天主要讲AQS中对独占锁的释放,如果大家把昨天“独占锁的获取”看完了,今天这篇文章将会很轻松!

 

AQS在独占模式下,对锁的释放只有release方法,而release方法其实就做了两件事:释放锁和唤醒后继Node(准确讲是Node中的线程,后面为了方便统一称为Node)。下面我们直接看源码吧


public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

我们可以看到,里面其实就只涉及到两个方法,tryRelease和unparkSuccessor,分别对应释放锁和唤醒后继Node,我们先梳理下release的整个逻辑再具体来看这两个方法

我们首先会通过tryRelease来释放锁,如果释放失败,则会返回false,否则就会进入到if分支中,在if分支中,我们必须满足(h != null && h.waitStatus != 0) 才会去唤醒后继Node。换句话说,我们只要满足下面条件中的一个,就不会去唤醒后继Node

  1. h==null

  2. h != null && h.waitStatus == 0

第一点h == null 这个条件很好理解,如果头结点为空了,那么同步队列肯定也是空的,所以此时是没有Node需要去唤醒的

第二点头结点不为空但状态为0是什么情况呢?“并发三板斧”中我们说过,我们在Node初始化时,会把Node的状态置为0,我们再回想下昨天“独占锁的获取”中的enq方法,我们是在这里将同步队列初始化的(即生成head与tail),此时同步队列中也是没有等待唤醒的Node

最后需要注意的是,release方法的返回值只与释放锁有关,和唤醒线程是没有关系的。好的,逻辑理清楚了,接下来就具体看下两个方法吧

 

1 tryRelease

这个方法是留给子类实现的,用来释放锁,如果释放成功则返回true,失败返回false,这个具体的实现我们放在子类中讲解,这里就不拓展了

 

2 unparkSuccessor

此方法是唤醒后继Node,我们看具体代码:

private void unparkSuccessor(Node node) {

    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

如果结点的waitStatus<0,说明此时waitStatus为SIGNAL(仅在独占模式下),我们此时需要将waitStatus置为0,因为待会会要唤醒后继节点

接着,我们拿到这个Node的后继Node,如果这个后继Node为空或者waitStatus属性大于0,说明这个Node已经不存在或者已被取消,则我们需要从尾结点开始往前遍历找到那个离head最近的需要被唤醒的Node,即进入第二个if分支

为啥是要从后往前遍历呢,这和我们“独占锁的获取”中讲的入队操作的顺序有关。独占模式下调用addWaiter方法入队时,是先将入队Node的前驱Node指向此时的尾Node,然后再通过CAS将tail指向入队Node,如果成功了(这步成功就标志着此Node已经入队成功,其他竞争的Node需要重新入队),才会将原来的尾Node的后继Node指向入队Node,代码如下

node.prev = pred;   //step1
if (compareAndSetTail(pred, node))   // step2
     pred.next = node;  // step3

假设我们此时有个Node正在入队,执行完step2,还未执行step3,如果unparkSuccessor采用从head往后遍历的话,此时是找不到这个新插入的Node的;但如果是采用从后往前遍历,则不会出现这个问题。如果对这个不太理解,可以去我上一篇文章“独占锁的获取”看看addWaiter方法的讲解,里面画了三幅图可以帮助理解并发时的入队情况,从而明白这里为什么需要从后往前遍历

最后遍历完后,我们会进入第三个if判断,如果s不为空,说明s就是队列中需要被唤醒的且最靠近head的节点,我们使用unpark方法将其唤醒就可以了

好了,独占锁的释放就讲完了,是不是比起独占锁的获取简单多了,当然,觉得简单的前提是你已经理解了独占锁的获取了

 

 

(未完)

欢迎大家关注我的公众号 “程序员进阶之路”,里面记录了一个非科班程序员的成长之路

                                                         

  • 15
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值