我的编程空间,编程开发者的网络收藏夹
学习永远不晚

AQS核心流程解析cancelAcquire方法

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

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

AQS核心流程解析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内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

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

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

AQS核心流程解析cancelAcquire方法

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

AQS核心流程解析cancelAcquire方法

可以清楚的看到在互斥锁和共享锁的拿锁过程中都是有调用此方法的,而cancelAcquire()方法是写在finally代码块中,并且使用failed标志位来控制cancelAcquire()方法的执行
2023-05-15

阿里云服务器解析域名的方法与流程

阿里云服务器是阿里云推出的一种高性能计算服务,能够满足用户对计算能力的需求。其中,解析域名是阿里云服务器中一个重要的功能,可以帮助用户在服务器上运行网页、应用程序等。本文将详细介绍阿里云服务器如何解析域名。一、阿里云服务器解析域名的方法阿里云服务器解析域名主要通过DNS服务器来实现。DNS服务器是域名系统的核心组
阿里云服务器解析域名的方法与流程
2023-12-09

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录