From 66e624c636b6b60c3f5410dc4ad818766512ba05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?11=E6=9D=A5=E4=BA=86?= Date: Thu, 19 Dec 2024 23:06:39 +0800 Subject: [PATCH] =?UTF-8?q?[docs=20update]=E5=AE=8C=E5=96=84=20AQS=20?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/java/concurrent/aqs.md | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/java/concurrent/aqs.md b/docs/java/concurrent/aqs.md index 257a0e06..e612b348 100644 --- a/docs/java/concurrent/aqs.md +++ b/docs/java/concurrent/aqs.md @@ -28,9 +28,9 @@ AQS 为构建锁和同步器提供了一些通用功能的实现。因此,使 ### AQS 快速了解 -在真正讲解 AQS 源码之前,需要对 AQS 有一个整体层面的认识。这里会先通过几个问题,先从整体层面上认识 AQS,了解 AQS 在整个 Java 并发中处于的层面,之后在学习 AQS 源码的过程中,才能更加了解同步器和 AQS 之间的关系。 +在真正讲解 AQS 源码之前,需要对 AQS 有一个整体层面的认识。这里会先通过几个问题,从整体层面上认识 AQS,了解 AQS 在整个 Java 并发中所位于的层面,之后在学习 AQS 源码的过程中,才能更加了解同步器和 AQS 之间的关系。 -- **`AQS` 的作用是什么?** +- **问题 1: `AQS` 的作用是什么?** 如果没有 AQS 的话,想要控制多线程的同步,需要通过 `synchronized` 关键字来完成,而 `synchronized` 又是 JVM 层面的,使用起来不太灵活。 @@ -38,7 +38,7 @@ AQS 为构建锁和同步器提供了一些通用功能的实现。因此,使 为了控制多线程同步访问共享资源,AQS 内部会提供一个队列,当线程获取不到共享资源时,会进入队列中等待,以此来实现多线程的同步访问。 -- **`AQS` 为什么使用 CLH 锁队列的变体?** +- **问题 2 :`AQS` 为什么使用 CLH 锁队列的变体?** CLH 锁是基于自旋锁的优化。 @@ -48,16 +48,17 @@ CLH 锁是基于自旋锁的优化。 而 AQS 又基于 CLH 锁进一步进行改进,源码作者(Doug Lea)称 AQS 内部的队列为 CLH 锁队列的变体,主要进行了两点改进: -- 由 **自旋** 优化为 **自旋 + 阻塞** :自旋操作的性能很高,但大量的自旋操作比较占用 CPU 资源,因此在 CLH 锁队列的变体中会先通过自旋尝试获取锁,如果失败再进行阻塞等待。 -- 由 **单向队列** 优化为 **双向队列** :在 CLH 锁队列的变体中,会对等待的线程进行阻塞操作,当队列前边的线程释放锁之后,需要对后边的线程进行唤醒,因此增加了 `next` 指针,成为了双向队列。 +1、由 **自旋** 优化为 **自旋 + 阻塞** :自旋操作的性能很高,但大量的自旋操作比较占用 CPU 资源,因此在 CLH 锁队列的变体中会先通过自旋尝试获取锁,如果失败再进行阻塞等待。 -- **`AQS` 和同步器( `ReentrantLock` 、 `Semaphore` 等)之间的关系是怎样的?** +2、由 **单向队列** 优化为 **双向队列** :在 CLH 锁队列的变体中,会对等待的线程进行阻塞操作,当队列前边的线程释放锁之后,需要对后边的线程进行唤醒,因此增加了 `next` 指针,成为了双向队列。 + +- **问题 3: `AQS` 和同步器( `ReentrantLock` 、 `Semaphore` 等)之间的关系是怎样的?** AQS 是一个抽象类,为同步器提供了执行框架。 **获取资源的流程** 已经在 AQS 中定义好了,具体如何获取资源则由同步器来实现。 同步器只需要基于 AQS 重写获取和释放资源的模板方法即可,因此 AQS 是底座,同步器是上层应用。 -- **`AQS` 的性能比较好,原因是什么?** +- **问题 4 :`AQS` 的性能比较好,原因是什么?** 因为 AQS 里使用了 `CAS` + `线程阻塞/唤醒` 。 @@ -65,7 +66,7 @@ AQS 是一个抽象类,为同步器提供了执行框架。 **获取资源的 但是如果一直通过 `CAS` 操作来更新数据,会比较占用 CPU。因此 AQS 同时结合了 `CAS` 和 `线程的阻塞/唤醒` 机制,当 `CAS` 没有成功获取资源时,会对线程进行阻塞,避免一直空转占用 CPU 资源。 -- **`AQS` 中为什么 Node 节点需要不同的状态?** +- **问题 5 :`AQS` 中为什么 Node 节点需要不同的状态?** AQS 中的 `waitStatus` 状态类似于 **状态机** ,通过不同状态来表明 Node 节点的不同含义,并且根据不同操作,来控制状态之间的流转。 @@ -592,17 +593,17 @@ private Node addWaiter(Node mode) { 由于 AQS 是底层同步工具,获取和释放资源的方法并没有提供具体实现,因此这里基于 `ReentrantLock` 来画图进行讲解。 -假设总共有 3 个线程同时获取锁,线程分别为 `T1` 、 `T2` 和 `T3` 。 +假设总共有 3 个线程尝试获取锁,线程分别为 `T1` 、 `T2` 和 `T3` 。 -此时,假设线程 `T1` 先获取到锁,线程 `T2` 排队等待获取资源。在线程 `T2` 进入队列之前,需要对 AQS 内部队列进行初始化。初始的 `head` 节点状态为 `0` 。初始化后的队列如下图: +此时,假设线程 `T1` 先获取到锁,线程 `T2` 排队等待获取锁。在线程 `T2` 进入队列之前,需要对 AQS 内部队列进行初始化。`head` 节点在初始化后状态为 `0` 。AQS 内部初始化后的队列如下图: ![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) -此时,线程 `T2` 尝试获取锁。由于线程 `T1` 持有所,因此线程 `T2` 会进入队列中等待获取锁。同时会将前继节点( `head` 节点)的状态由 `0` 修改为 `SIGNAL` ,表示需要对 `head` 节点的后继节点进行唤醒。此时,AQS 内部队列如下图所示: +此时,线程 `T2` 尝试获取锁。由于线程 `T1` 持有锁,因此线程 `T2` 会进入队列中等待获取锁。同时会将前继节点( `head` 节点)的状态由 `0` 更新为 `SIGNAL` ,表示需要对 `head` 节点的后继节点进行唤醒。此时,AQS 内部队列如下图所示: ![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) -此时,线程 `T2` 尝试获取锁。由于线程 `T1` 持有所,因此线程 `T2` 会进入队列中等待获取锁。同时会将前继节点(线程 `T2` 节点)的状态由 `0` 修改为 `SIGNAL` ,表示线程 `T2` 节点需要对后继节点进行唤醒。此时,AQS 内部队列如下图所示: +此时,线程 `T3` 尝试获取锁。由于线程 `T1` 持有锁,因此线程 `T3` 会进入队列中等待获取锁。同时会将前继节点(线程 `T2` 节点)的状态由 `0` 更新为 `SIGNAL` ,表示线程 `T2` 节点需要对后继节点进行唤醒。此时,AQS 内部队列如下图所示: ![AQS acquire and release process.drawio](https://11laile-note-img.oss-cn-beijing.aliyuncs.com/AQS%20acquire%20and%20release%20process.drawio.png) @@ -612,9 +613,7 @@ private Node addWaiter(Node mode) { ![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) -此时,线程 `T2` 释放锁,会唤醒后继节点 `T3` 。线程 `T3` 获取到锁之后,同样也退出等待队列,即将线程 `T3` 节点变为 `head` 节点来退出资源获取的等待。 - -此时 AQS 内部队列如下所示: +此时,假设线程 `T2` 释放锁,会唤醒后继节点 `T3` 。线程 `T3` 获取到锁之后,同样也退出等待队列,即将线程 `T3` 节点变为 `head` 节点来退出资源获取的等待。此时 AQS 内部队列如下所示: ![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)