文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

AQS核心流程解析cancelAcquire方法

2023-05-15 08:11

关注

引出问题

首先,先考虑一个问题,什么条件会触发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内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯