JUC——AQS部分疑问及解答

6 篇文章 0 订阅
5 篇文章 0 订阅

以下是个人阅读源码时的一些疑问及个人解答,不涉及源码论证,如果想结合源码来论证的话,可以先去看:JUC——AQS源码解析

英文好的也可以去看看https://gee.cs.oswego.edu/dl/papers/aqs.pdf,这份pdf里写了很多AQS设计的初衷以及为什么这么设计

1.Node节点的next指针是可靠的嘛?

不可靠,这与节点入队方法enq有关。

节点入队时,步骤依次为:1.节点prev指向尾节点——2.CAS更新节点为尾节点——3.前任节点(旧尾节点)next指向该节点(现任尾节点)

从步骤可看出,在CAS更新了尾节点后,若此时发生了线程调度,那么其他线程使用队列时,可能就会发现某个节点的next为null,但实际上只是还未来得及更新而已,这时就需要使用尾部遍历来检查队列。

(引申:队列是可能出现暂时性分叉的,假设在并发的极端情况下,多个线程都执行了上述步骤1,此时多个节点指向prev,但只有一个节点能成功执行步骤2,其他线程会继续循环,直至入队成功。这种情况是暂时的,而且并不会影响到队列)

2.Node节点的prev指针是可靠的嘛?

可靠

从问题1可知,节点是会先更新节点的prev指针,然后再CAS更新尾节点。虽然这种情况可能会在极端情况下出现短暂的队列分叉,但是也确保了成功执行CAS的节点的prev指针不可能为空,它的prev必然正确指向前任节点。

3.AQS中为什么几乎使用尾节点遍历而不使用头节点遍历?(例如:节点唤醒方法unparkSuccessor等)

因为next指针不可靠,而prev节点可靠。从尾节点依靠prev遍历,必然能遍历到整个队列(当然,还未正式入队列的节点不算)

4.AQS为什么使用双向链表?

先说结论,AQS中希望队列在入队时只需要对tail操作,在出队时只涉及head更新。部分是为了处理取消和中断操作。这是AQS中Node类的注释原话,该注释还分别介绍了prev和next的作用:

prev指针主要是处理取消(也就是中断锁的获取),当节点取消时,它的后继节点会重新链接到未取消的前任节点

next指针实现阻塞机制(也就是阻塞唤醒机制),当线程释放锁时,会唤醒后继节点

个人看法,使用双向链表,就是为了更快:

  • 入队时,直接更改当前节点的prev和前任节点的next,即可完成入队
  • 抢锁时,直接获取当前节点的prev,就能知道是否应该抢锁(prev是否指针头节点),若不满足抢锁条件,也可以设置prev节点的状态后将自己休眠,避免持续占用CPU
  • 取消锁时,直接获取当前节点的prev及next,将前任节点的next指针及后继节点的prev指针更改下,即可将当前节点移出队列
  • 释放锁时,直接获取当前节点的next,将该节点唤醒即可
  • 出队时,被唤醒的节点在抢锁完成后,直接将自己的prev改为null,前任节点就被移出了队列

上面列举了大部分使用到prev及next的地方(可能会漏掉),我们可以看到,如果不使用双向链表,这里面很多场景都要去从头遍历,很浪费效率,而使用了双向链表,几乎能非常快速的完成操作

当然了,上面也只是理想情况下的操作,如果出现了极端情况,也是会从尾节点遍历进行节点检查(例如:某节点next为null或者为已取消节点,那么此时无法唤醒next指向的节点,只能通过tail尾节点来向上遍历,找到最近的一个正常节点。)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值