AQS中那些不得不说的理论知识

最近我根据上述的技术体系图搜集了几十套腾讯、头条、阿里、美团等公司21年的面试题,把技术点整理成了视频(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

AQS全称为AbstractQueuedSynchronizer(后面都以AQS简称),翻译过来叫做抽象队列同步器,是一个抽象类,其实也是一个单机多线程基础同步框架,这个框架通过对同步状态的原子性管理,实现对多线程的管理。子类应该定义一个非公开的内部类继承AQS,并实现其中方法。此部分介绍以独占模式为主。

子类根据需要需要重写下面的方法:

  • tryAcquire

  • tryRelease

  • tryReleaseShared

  • isHeldExclusively等方法,并确保是线程安全的。

二、AQS实现的基础


1、CAS:是compareandswap的简称,从字面上理解就是比较并更新。CAS操作是由Unsafe工具类来实现的,其操作具有原子性,我们一般通过CAS来改变状态。(状态被volatile修饰,因此具有可见性和有序性,所以CAS改变状态时是线程安全的)

2、队列:用来保存等待操作的资源,其数据结构一般为链表。当线程的请求在短时间内得不到满足时,线程会被包装成某种类型的数据结构放入队列中,当条件满足时则会拿出队列去重新获取锁。

3、状态:AQS及其子类的核心,AQS及其子类所有操作都是依据状态进进行的。状态一般会设置成volatile,保证其具有可见性,一定程度上具有有序性。它为 0 的时候代表没有线程占有锁,可以去争抢这个锁,用 CAS 将 state 设为 1,如果 CAS 成功,说明抢到了锁,这样其他线程就抢不到了。如果锁重入的话,state进行 +1 就可以,解锁就是减 1,直到 state 又变为 0,代表释放锁,所以 lock()unlock()必须要配对。共享模式的话,状态也是进行+1,解锁就是减一。

4、locksupport类的park和unpark方法。AQS队列的阻塞调用了Unsafe类中的park,park方法则借助于操作系统的实现来进行阻塞的,借此实现阻塞的两个特性:

  1. 不耗 CPU 等待;

  2. 线程安全。

三、AQS术语解释


1、队列

整个框架的关键就是如何管理被阻塞的线程的队列,该队列是严格的FIFO队列,因此,框架不支持基于优先级的同步。队列根据需要分为同步队列和条件队列。它们都是通过下面要介绍的AQS内部类Node来实现的。

1.1、 同步队列(阻塞队列)

同步队列是双向链表结构,既然能做双向链表,它可以用来保存等待的线程以及线程的状态等信息。头结点是一个哨兵节点(是一个附加的链表节点,该节点作为第一个节点,但是它其实并不存储任何东西,只是为了操作的方便而引入的)。

1.2、 条件队列(等待队列)

当使用Condition的时候,等待队列的概念就出来了。Condition的获取一般都要与一个锁Lock相关,一个锁上面可以生产多个Condition。即一个锁内的代码块可以新建多个Condition对象。

1.3 同步队列和条件队列的关系

Condition接口的主要实现类是AQS的内部类ConditionObject,每个Condition对象都包含一个等待队列。该队列是Condition对象实现等待/通知的关键。AQS中同步队列与等待队列的关系如下:

78e2b82010494ba91a3425bc35333baa.png

2、Node

AQS的一个内部类。用来包装竞争资源的线程,并将其组装成链表结构。

2.1 prev

同步队列的前驱节点,条件队列没有这个概念。

2.2 next

同步队列的后继节点,条件队列没有这个概念。

2.3 thread

竞争资源的线程。

2.4 nextWaiter

条件队列的下一个节点,同步队列没有这个概念。

2.5 waitStatus

队列中节点的等待状态。

  • static final int CANCELLED = 1; 此节点的线程被取消 独占模式 共享模式

  • static final int SIGNAL = -1; 此节点的后继节点线程被挂起,需要被唤醒 独占模式

  • static final int CONDITION = -2; 此节点的线程在等待信号,也表明当前节点不在同步队列中,而在条件队列中

  • static final int PROPAGATE = -3; 此节点下一个acquireShared应该无条件传播   共享模式

这四个属性就是waitStatus属性的具体状态,还有一个隐式的具体状态,即waitStatus初始化时为0。在独占模式下,我们只需要用到CANCELLED和SIGNAL,这里需要注意的是SIGNAL,它代表的不是自己线程的状态,而是它后继节点的状态,当一个节点的waitStatus被置为SIGNAL时,表明此节点的后继节点被挂起,当此节点释放锁或被取消放弃拿锁时,应该唤醒后继节点。而在共享模式时,我们会用到CANCELLED和PROPAGATE

3、ConditionObject

是AQS的内部类ConditionObject。

3.1 firstWaiter

条件队列的第一个节点。

3.2 lastWaiter

条件队列的最后一个节点。

3.2 await()

3.2.1 创建一个条件队列节点,把自己加入到条件队列中,必要的时候初始化条件队列;

3.2.2 因为调用await的线程都持有锁,所以接下来需要执行AQS的release方法释放当前线程持有的锁,即让出锁,让其他线程执行;

3.2.3 利用park方法将当前线程挂起,等待唤醒;

3.2.4 其他线程调用signal()方法唤醒该线程(该方法是隐式的,在代码中没有体现,因为是多线程执行,体现在signal())

  • 将节点从条件队列转移到等待队列

  • 调用unpark方法唤醒线程

  • 重新竞争锁。这个步骤也很关键,这里可以区分公平锁和非公平锁

3.3 signal()

3.3.1 将条件队列中的的节点(firstWaiter)转移到同步队列中

3.3.2 把刚转移到同步队列中的节点前驱的waitstatus改为SIGNAL(-1),用来唤醒后继节点。

4、队列同步器模式

4.1 共享模式

同一时间只有一个线程能拿到锁执行。

4.2 独占模式

同一时间有多个线程可以拿到锁协同工作。

4.3 公平锁

多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。

4.4 非公平锁

多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。

4.5 可重入锁

广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁。

在ReentrantLock上的体现:

final boolean nonfairTryAcquire(int acquires) {  //这里我就跳过着讲解了,ReentrantLock模式使用的是非公平锁,这样能提高系统的响应性能

final Thread current = Thread.currentThread();

最后

无论是哪家公司,都很重视基础,大厂更加重视技术的深度和广度,面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。

针对以上面试技术点,我在这里也做一些分享,希望能更好的帮助到大家。

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

些分享,希望能更好的帮助到大家。

[外链图片转存中…(img-rGMGOtHQ-1715811049365)]

[外链图片转存中…(img-abezwHhT-1715811049366)]

[外链图片转存中…(img-88xNyxmG-1715811049366)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值