并发编程面试题2

一、AQS高频问题:

1.1 AQS是什么?

AQS是一个抽象队列同步器(Abstract Queued Synchronizer),本质是一个抽象类。

AQS中有一个核心属性state,其次还有一个双向链表以及一个单向链表。

  • state是基于volatile修饰,再基于CAS修改,同时可以保证三大特性(原子性、可见性、有序性)。
  • 双向链表由Node对象组成。
  • Condition内部类中提供了一个由Node对象组成的单向链表。

AQS是JUC下大量工具的基础类,很多工具都基于AQS实现,比如Lock锁、CountDownLatch、Semaphore、线程池等等。

state是啥:state是一个int类型的数值,表示同步状态,具体状态由子类实现。

condition和单向链表是啥:sync提供了wait方法和notify方法,Lock锁也需要实现这种机制,Lock锁基于AQS内部的Condition实现了await和signal方法(对标sync的wait和notify)。

sync在线程持有锁时,执行wait方法,会将线程扔到WaitSet等待池中排队,等待唤醒;Lock在线程持有锁时,执行await方法,会将线程封装为Node对象,扔到Condition单向链表中,等待唤醒。

Condition在做了什么:将持有锁的线程封装为Node扔到Condition单向链表,同时挂起线程。如果线程唤醒了,就将Condition中的Node扔到AQS的双向链表等待获取锁。

1.2 唤醒线程时,AQS为什么从后往前遍历?

如果线程没有获取到资源,就需要将线程封装为Node对象,安排到AQS的双向链表中排队,并且可能会挂起线程。

在唤醒线程时,head节点的next是第一个要被唤醒的。如果head的next节点取消了,AQS的逻辑是从tail节点往前遍历,找到离head最近的有效节点。

为了理解这个问题,需要了解一个Node对象是如何添加到双向链表中的。基于addWaiter方法,先将当前Node的prev指向tail节点,再将tail指向当前节点,再让prev节点指向当前节点。如下图所示,如果只执行到第二步,Node加入到AQS队列中,但从prev节点往后找不到当前节点。

image.png

1.3 AQS为什么用双向链表?

因为AQS中存在取消节点的操作,节点被取消后,需要从AQS的双向链表中断开连接并保证链表的完整性:

  • 需要将prev节点的next指针指向next节点。
  • 需要将next节点的prev指针指向prev节点。

如果是单向链表,需要遍历整个链表才能完成上述操作,浪费资源。

1.4 AQS为什么要有一个虚拟的head节点?

有一个哨兵节点更方便操作。另一个原因是AQS内部每个Node都有一些状态,这些状态不仅针对自己,还针对后续节点:

  • 1:当前节点取消。
  • 0:默认状态。
  • -1:当前节点的后继节点挂起。
  • -2:当前节点在Condition队列中(await将线程挂起)。
  • -3:当前是共享锁,唤醒时后续节点依
  • 23
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬砖的小熊猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值