AQS核心流程解析cancelAcquire方法

 更新时间:2023年04月10日 09:48:29   作者:程序员李哈  
可以清楚的看到在互斥锁和共享锁的拿锁过程中都是有调用此方法的,而cancelAcquire()方法是写在finally代码块中,并且使用failed标志位来控制cancelAcquire()方法的执行

引出问题

首先,先考虑一个问题,什么条件会触发cancelAcquire()方法?

cancelAcquire()方法的反向查找

可以清楚的看到在互斥锁和共享锁的拿锁过程中都是有调用此方法的,而cancelAcquire()方法是写在finally代码块中,并且使用failed标志位来控制cancelAcquire()方法的执行。可以得出,在触发异常的情况下会执行cancelAcquire()方法。

响应中断的获锁方法

可以清楚的看到,这里是响应异常,如果发生了异常,比如中断异常,那么当前线程Node需要做出取消的操作,那么下面详细的说明cancelAcquire()方法。

private void cancelAcquire(Node node) {
    // Ignore if node doesn't exist
    if (node == null)
        return;
    // 当前节点的线程指向置为null
    node.thread = null;
    // 协同取消的处理。
    // 这里是判断当前节点的上一个节点的状态是否是取消状态(状态大于0只有是取消状态)
    // 如果上一个节点是取消状态,那么继续往上遍历,直到找到状态为小于0的状态节点。
    // 并且把当前节点的prev指向非取消节点。
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;
    // 得到没有取消节点的下一个节点。
    Node predNext = pred.next;
    // 因为当前cancelAcquire()方法就是取消的处理
    // 所以将当前节点设置为取消状态。
    node.waitStatus = Node.CANCELLED;
    // 如果当前取消的节点是tail节点,也就是最后一个节点
    // 那么就把tail指针指向上面while循环遍历出的prev节点(因为要指向一个没有被取消的节点)。
    if (node == tail && compareAndSetTail(node, pred)) {
        // help GC
        // 为什么说help GC呢?
        // 因为把prev的next节点设置为null,
        // 这样GC ROOT扫描发现没有根节点的引用。
        compareAndSetNext(pred, predNext, null);
    } else {
        // 走到else代表当前节点不是tail节点,或者是cas操作的时候tail发生了变化
        // 如果不是tail节点,不能直接把tail节点指向到上面while循环得出的prev节点
        int ws;
        // 这里是的if代码块,是为了尝试一次,如果不成功再去复杂的处理。
        // 这里的if判断条件如下:
            // 1.如果上面while循环得到的prev节点不是head节点
            // 2.如果上面while循环得到的prev节点为-1,如果不为-1,cas改变成-1也。
            // 3.如果上面while循环得到的rpev节点的线程指向不为null(如果为null代表在取消的过程中)
        // 因为&&是拼接,所以上面任意一个条件为false就会进入到else条件中。
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            // 进到这里代表这次尝试成功了。
            // 得到当前节点的下一个节点
            // 然后把前面while循环得到的prev节点的next指向当前节点的next节点。
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            // 直接唤醒当前节点的下一个节点。
            // 唤醒的目的是为了去执行shouldParkAfterFailedAcquire方法去处理取消节点。
            unparkSuccessor(node);
        }
        // 把当前节点的下一个节点指向自己.
        // help gc。
        node.next = node; // help GC
    }
}

比较复杂,所以笔者为了读者的观看顺利, 下面会拆分步骤,并且画图来理解。

跳过已经取消的节点,找到一个非取消的节点

// Skip cancelled predecessors
// 跳过已经被取消的节点
Node pred = node.prev;
while (pred.waitStatus > 0)
    node.prev = pred = pred.prev;

更新正常节点的链表

当前取消节点是tail节点的情况

if (node == tail && compareAndSetTail(node, pred)) {
    compareAndSetNext(pred, predNext, null);
} else {
    // 后面讲
}

当前取消节点是非tail节点的情况

// 当前取消的节点不为tail节点的情况
int ws;
// if的逻辑可以理解为尝试一次。
// 代表while循环得到的prev节点不是head节点
// 代表while循环得到的prev节点是可唤醒的正常节点
// 节点while循环得到的prev节点不是待取消节点
if (pred != head &&
    ((ws = pred.waitStatus) == Node.SIGNAL ||
     (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
    pred.thread != null) {
    Node next = node.next;
    // 当前节点的下一个节点也是正常的情况(非取消)
    if (next != null && next.waitStatus <= 0)
        compareAndSetNext(pred, predNext, next);
} else {
    // 尝试失败,只能走比较狠的逻辑去处理了。
    unparkSuccessor(node);
}
node.next = node; // help GC

如果if的判断能通过,那就代表当前这次尝试是成功的,成功了就把链表都链上。

问题来了,为什么这里不把Node.next(取消节点的下一个节点)和Node(当前取消节点)的链给断开,不断开的话,JVM是无法回收掉Node(当前取消节点),那不是内存泄漏了?

Doug Lea这里是不是写的有问题?

nonono.

当正常唤醒节点时,抢到锁的节点会执行

private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

看到else条件下unparkSuccessor(node);的执行逻辑。

这里已经相当难描述清楚,笔者会执行流程和代码都已经写明白。

这里其实就是一个唤醒操作,唤醒的意义在于去执行shouldParkAfterFailedAcquire()方法从后往前遍历找到一个waitStatus不为1的节点,然后链起来。

cancelAcquire
    |->unparkSuccessor(node当前取消节点)
        |->LockSupport.unpark(node.next.thread)
            |->shouldParkAfterFailedAcquire(node(取消的节点),node.next(取消的节点的下一个节点))
// Node pred 是取消的节点
// Node node 是取消的节点的下一个节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        return true;
    // 遍历找到一个非取消的节点
    // 并且把当前取消的节点的下一个节点与找到的非取消节点双向链起来。
    if (ws > 0) {
        do {
            // 往前走
            node.prev = pred = pred.prev;     // 链起来
        } while (pred.waitStatus > 0);        // 循环条件,所以找到一个小于等于0的节点就会退出
        pred.next = node;              // 链起来
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

最终唤醒节点,走到shouldParkAfterFailedAcquire()方法中。从后往前的遍历找到正常的节点

到此这篇关于AQS核心流程解析cancelAcquire方法的文章就介绍到这了,更多相关AQS cancelAcquire内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • mybatis输入映射和输出映射实例详解

    mybatis输入映射和输出映射实例详解

    这篇文章主要介绍了mybatis输入映射和输出映射,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-05-05
  • springcloud 熔断器Hystrix的具体使用

    springcloud 熔断器Hystrix的具体使用

    本篇文章主要介绍了springcloud 熔断器Hystrix的具体使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-02-02
  • Spring Data JPA框架的核心概念与Repository接口详解

    Spring Data JPA框架的核心概念与Repository接口详解

    Spring Data JPA是Spring基于JPA规范的基础上封装的⼀套 JPA 应⽤框架,可使开发者⽤极简的代码即可实现对数据库的访问和操作,本篇我们来了解Spring Data JPA框架的核心概念与Repository接口
    2022-04-04
  • SpringMVC之简单的增删改查示例(SSM整合)

    SpringMVC之简单的增删改查示例(SSM整合)

    本篇文章主要介绍了SpringMVC之简单的增删改查示例(SSM整合),这个例子是基于SpringMVC+Spring+Mybatis实现的。有兴趣的可以了解一下。
    2017-03-03
  • Java同步函数代码详解

    Java同步函数代码详解

    这篇文章主要介绍了Java线程中的同步函数的相关内容,涉及了实例代码,需要的朋友,可以参考下。
    2017-10-10
  • Java Scala偏函数与偏应用函数超详细讲解

    Java Scala偏函数与偏应用函数超详细讲解

    Scala是一种多范式的编程语言,支持面向对象和函数式编程。Scala也支持异常处理,即在程序运行过程中发生意外或错误时,采取相应的措施
    2023-04-04
  • 使用反射方式获取JPA Entity的属性和值

    使用反射方式获取JPA Entity的属性和值

    这篇文章主要介绍了使用反射方式获取JPA Entity的属性和值,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Java日期处理工具类DateUtils详解

    Java日期处理工具类DateUtils详解

    这篇文章主要为大家详细介绍了Java日期处理工具类DateUtils的相关代码,包含日期和时间常用操作,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-12-12
  • springboot2.3 整合mybatis-plus 高级功能(图文详解)

    springboot2.3 整合mybatis-plus 高级功能(图文详解)

    这篇文章主要介绍了springboot2.3 整合mybatis-plus 高级功能,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • Java中类转json的基类实现

    Java中类转json的基类实现

    这篇文章主要介绍了Java中类转json的基类实现,需要的朋友可以参考下
    2021-01-01

最新评论