@@ -28,17 +28,17 @@ AQS 为构建锁和同步器提供了一些通用功能的实现。因此,使
2828
2929### AQS 快速了解
3030
31- 在真正讲解 AQS 源码之前,需要对 AQS 有一个整体层面的认识。这里会先通过几个问题,先从整体层面上认识 AQS,了解 AQS 在整个 Java 并发中处于的层面 ,之后在学习 AQS 源码的过程中,才能更加了解同步器和 AQS 之间的关系。
31+ 在真正讲解 AQS 源码之前,需要对 AQS 有一个整体层面的认识。这里会先通过几个问题,从整体层面上认识 AQS,了解 AQS 在整个 Java 并发中所位于的层面 ,之后在学习 AQS 源码的过程中,才能更加了解同步器和 AQS 之间的关系。
3232
33- - ** ` AQS ` 的作用是什么?**
33+ - ** 问题 1: ` AQS ` 的作用是什么?**
3434
3535如果没有 AQS 的话,想要控制多线程的同步,需要通过 ` synchronized ` 关键字来完成,而 ` synchronized ` 又是 JVM 层面的,使用起来不太灵活。
3636
3737因此,需要在 Java 语言层面实现一套同步控制器,也就是 AQS(抽象队列同步器)。
3838
3939为了控制多线程同步访问共享资源,AQS 内部会提供一个队列,当线程获取不到共享资源时,会进入队列中等待,以此来实现多线程的同步访问。
4040
41- - ** ` AQS ` 为什么使用 CLH 锁队列的变体?**
41+ - ** 问题 2 : ` AQS ` 为什么使用 CLH 锁队列的变体?**
4242
4343CLH 锁是基于自旋锁的优化。
4444
@@ -48,24 +48,25 @@ CLH 锁是基于自旋锁的优化。
4848
4949而 AQS 又基于 CLH 锁进一步进行改进,源码作者(Doug Lea)称 AQS 内部的队列为 CLH 锁队列的变体,主要进行了两点改进:
5050
51- - 由 ** 自旋** 优化为 ** 自旋 + 阻塞** :自旋操作的性能很高,但大量的自旋操作比较占用 CPU 资源,因此在 CLH 锁队列的变体中会先通过自旋尝试获取锁,如果失败再进行阻塞等待。
52- - 由 ** 单向队列** 优化为 ** 双向队列** :在 CLH 锁队列的变体中,会对等待的线程进行阻塞操作,当队列前边的线程释放锁之后,需要对后边的线程进行唤醒,因此增加了 ` next ` 指针,成为了双向队列。
51+ 1、由 ** 自旋** 优化为 ** 自旋 + 阻塞** :自旋操作的性能很高,但大量的自旋操作比较占用 CPU 资源,因此在 CLH 锁队列的变体中会先通过自旋尝试获取锁,如果失败再进行阻塞等待。
5352
54- - ** ` AQS ` 和同步器( ` ReentrantLock ` 、 ` Semaphore ` 等)之间的关系是怎样的?**
53+ 2、由 ** 单向队列** 优化为 ** 双向队列** :在 CLH 锁队列的变体中,会对等待的线程进行阻塞操作,当队列前边的线程释放锁之后,需要对后边的线程进行唤醒,因此增加了 ` next ` 指针,成为了双向队列。
54+
55+ - ** 问题 3: ` AQS ` 和同步器( ` ReentrantLock ` 、 ` Semaphore ` 等)之间的关系是怎样的?**
5556
5657AQS 是一个抽象类,为同步器提供了执行框架。 ** 获取资源的流程** 已经在 AQS 中定义好了,具体如何获取资源则由同步器来实现。
5758
5859同步器只需要基于 AQS 重写获取和释放资源的模板方法即可,因此 AQS 是底座,同步器是上层应用。
5960
60- - ** ` AQS ` 的性能比较好,原因是什么?**
61+ - ** 问题 4 : ` AQS ` 的性能比较好,原因是什么?**
6162
6263因为 AQS 里使用了 ` CAS ` + ` 线程阻塞/唤醒 ` 。
6364
6465在 AQS 的实现里,大量使用了 ` CAS ` 操作,` CAS ` 基于内存地址直接进行数据修改,保证并发安全的同时,性能也很好。
6566
6667但是如果一直通过 ` CAS ` 操作来更新数据,会比较占用 CPU。因此 AQS 同时结合了 ` CAS ` 和 ` 线程的阻塞/唤醒 ` 机制,当 ` CAS ` 没有成功获取资源时,会对线程进行阻塞,避免一直空转占用 CPU 资源。
6768
68- - ** ` AQS ` 中为什么 Node 节点需要不同的状态?**
69+ - ** 问题 5 : ` AQS ` 中为什么 Node 节点需要不同的状态?**
6970
7071AQS 中的 ` waitStatus ` 状态类似于 ** 状态机** ,通过不同状态来表明 Node 节点的不同含义,并且根据不同操作,来控制状态之间的流转。
7172
@@ -592,17 +593,17 @@ private Node addWaiter(Node mode) {
592593
593594由于 AQS 是底层同步工具,获取和释放资源的方法并没有提供具体实现,因此这里基于 ` ReentrantLock ` 来画图进行讲解。
594595
595- 假设总共有 3 个线程同时获取锁 ,线程分别为 ` T1 ` 、 ` T2 ` 和 ` T3 ` 。
596+ 假设总共有 3 个线程尝试获取锁 ,线程分别为 ` T1 ` 、 ` T2 ` 和 ` T3 ` 。
596597
597- 此时,假设线程 ` T1 ` 先获取到锁,线程 ` T2 ` 排队等待获取资源 。在线程 ` T2 ` 进入队列之前,需要对 AQS 内部队列进行初始化。初始的 ` head ` 节点状态为 ` 0 ` 。初始化后的队列如下图 :
598+ 此时,假设线程 ` T1 ` 先获取到锁,线程 ` T2 ` 排队等待获取锁 。在线程 ` T2 ` 进入队列之前,需要对 AQS 内部队列进行初始化。` head ` 节点在初始化后状态为 ` 0 ` 。AQS 内部初始化后的队列如下图 :
598599
599600![ AQS acquire and release process 5.drawio] ( https://11laile-note-img.oss-cn-beijing.aliyuncs.com/AQS%20acquire%20and%20release%20process%205.drawio-173461521802737.png )
600601
601- 此时,线程 ` T2 ` 尝试获取锁。由于线程 ` T1 ` 持有所 ,因此线程 ` T2 ` 会进入队列中等待获取锁。同时会将前继节点( ` head ` 节点)的状态由 ` 0 ` 修改为 ` SIGNAL ` ,表示需要对 ` head ` 节点的后继节点进行唤醒。此时,AQS 内部队列如下图所示:
602+ 此时,线程 ` T2 ` 尝试获取锁。由于线程 ` T1 ` 持有锁 ,因此线程 ` T2 ` 会进入队列中等待获取锁。同时会将前继节点( ` head ` 节点)的状态由 ` 0 ` 更新为 ` SIGNAL ` ,表示需要对 ` head ` 节点的后继节点进行唤醒。此时,AQS 内部队列如下图所示:
602603
603604![ AQS acquire and release process 4.drawio] ( https://11laile-note-img.oss-cn-beijing.aliyuncs.com/AQS%20acquire%20and%20release%20process%204.drawio-173461538992839.png )
604605
605- 此时,线程 ` T2 ` 尝试获取锁。由于线程 ` T1 ` 持有所 ,因此线程 ` T2 ` 会进入队列中等待获取锁。同时会将前继节点(线程 ` T2 ` 节点)的状态由 ` 0 ` 修改为 ` SIGNAL ` ,表示线程 ` T2 ` 节点需要对后继节点进行唤醒。此时,AQS 内部队列如下图所示:
606+ 此时,线程 ` T3 ` 尝试获取锁。由于线程 ` T1 ` 持有锁 ,因此线程 ` T3 ` 会进入队列中等待获取锁。同时会将前继节点(线程 ` T2 ` 节点)的状态由 ` 0 ` 更新为 ` SIGNAL ` ,表示线程 ` T2 ` 节点需要对后继节点进行唤醒。此时,AQS 内部队列如下图所示:
606607
607608![ AQS acquire and release process.drawio] ( https://11laile-note-img.oss-cn-beijing.aliyuncs.com/AQS%20acquire%20and%20release%20process.drawio.png )
608609
@@ -612,9 +613,7 @@ private Node addWaiter(Node mode) {
612613
613614![ AQS acquire and release process 2.drawio] ( https://11laile-note-img.oss-cn-beijing.aliyuncs.com/AQS%20acquire%20and%20release%20process%202.drawio-173461691867746.png )
614615
615- 此时,线程 ` T2 ` 释放锁,会唤醒后继节点 ` T3 ` 。线程 ` T3 ` 获取到锁之后,同样也退出等待队列,即将线程 ` T3 ` 节点变为 ` head ` 节点来退出资源获取的等待。
616-
617- 此时 AQS 内部队列如下所示:
616+ 此时,假设线程 ` T2 ` 释放锁,会唤醒后继节点 ` T3 ` 。线程 ` T3 ` 获取到锁之后,同样也退出等待队列,即将线程 ` T3 ` 节点变为 ` head ` 节点来退出资源获取的等待。此时 AQS 内部队列如下所示:
618617
619618![ AQS acquire and release process 3.drawio] ( https://11laile-note-img.oss-cn-beijing.aliyuncs.com/AQS%20acquire%20and%20release%20process%203.drawio-173461705733148.png )
620619
0 commit comments