Skip to content

Commit f718447

Browse files
committed
[docs update]完善aps部分的描述
1 parent 7605688 commit f718447

File tree

2 files changed

+19
-10
lines changed

2 files changed

+19
-10
lines changed

docs/java/concurrent/aqs.md

+18-9
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,17 @@ AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求
3030

3131
CLH 锁是对自旋锁的一种改进,是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系),暂时获取不到锁的线程将被加入到该队列中。AQS 将每条请求共享资源的线程封装成一个 CLH 队列锁的一个结点(Node)来实现锁的分配。在 CLH 队列锁中,一个节点表示一个线程,它保存着线程的引用(thread)、 当前节点在队列中的状态(waitStatus)、前驱节点(prev)、后继节点(next)。
3232

33-
CLH 队列锁结构如下图所示
33+
CLH 队列结构如下图所示
3434

35-
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/40cb932a64694262993907ebda6a0bfe~tplv-k3u1fbpfcp-zoom-1.image)
35+
![CLH 队列结构](https://oss.javaguide.cn/github/javaguide/java/concurrent/clh-queue-structure.png)
3636

3737
关于 AQS 核心数据结构-CLH 锁的详细解读,强烈推荐阅读 [Java AQS 核心数据结构-CLH 锁 - Qunar 技术沙龙](https://mp.weixin.qq.com/s/jEx-4XhNGOFdCo4Nou5tqg) 这篇文章。
3838

39-
AQS(`AbstractQueuedSynchronizer`)的核心原理图(图源[Java 并发之 AQS 详解](https://www.cnblogs.com/waterystone/p/4920797.html))如下
39+
AQS(`AbstractQueuedSynchronizer`)的核心原理图:
4040

41-
![](https://oss.javaguide.cn/github/javaguide/CLH.png)
41+
![CLH 队列](https://oss.javaguide.cn/github/javaguide/java/concurrent/clh-queue-state.png)
4242

43-
AQS 使用 **int 成员变量 `state` 表示同步状态**,通过内置的 **线程等待队列** 来完成获取资源线程的排队工作。
43+
AQS 使用 **int 成员变量 `state` 表示同步状态**,通过内置的 **FIFO 线程等待/同步队列** 来完成获取资源线程的排队工作。
4444

4545
`state` 变量由 `volatile` 修饰,用于展示当前临界资源的获锁情况。
4646

@@ -66,9 +66,13 @@ protected final boolean compareAndSetState(int expect, int update) {
6666
}
6767
```
6868

69-
`ReentrantLock` 为例,`state` 初始值为 0,表示未锁定状态。A 线程 `lock()` 时,会调用 `tryAcquire()` 独占该锁并将 `state+1` 。此后,其他线程再 `tryAcquire()` 时就会失败,直到 A 线程 `unlock()``state=`0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A 线程自己是可以重复获取此锁的(`state` 会累加),这就是可重入的概念。但要注意,获取多少次就要释放多少次,这样才能保证 state 是能回到零态的。相关阅读:[从 ReentrantLock 的实现看 AQS 的原理及应用 - 美团技术团队](./reentrantlock.md)
69+
以可重入的互斥锁 `ReentrantLock` 为例,它的内部维护了一个 `state` 变量,用来表示锁的占用状态。`state` 的初始值为 0,表示锁处于未锁定状态。当线程 A 调用 `lock()` 方法时,会尝试通过 `tryAcquire()` 方法独占该锁,并让 `state` 的值加 1。如果成功了,那么线程 A 就获取到了锁。如果失败了,那么线程 A 就会被加入到一个等待队列(CLH 队列)中,直到其他线程释放该锁。假设线程 A 获取锁成功了,释放锁之前,A 线程自己是可以重复获取此锁的(`state` 会累加)。这就是可重入性的体现:一个线程可以多次获取同一个锁而不会被阻塞。但是,这也意味着,一个线程必须释放与获取的次数相同的锁,才能让 `state` 的值回到 0,也就是让锁恢复到未锁定状态。只有这样,其他等待的线程才能有机会获取该锁
7070

71-
再以 `CountDownLatch` 以例,任务分为 N 个子线程去执行,`state` 也初始化为 N(注意 N 要与线程个数一致)。这 N 个子线程是并行执行的,每个子线程执行完后`countDown()` 一次,state 会 CAS(Compare and Swap) 减 1。等到所有子线程都执行完后(即 `state=0` ),会 `unpark()` 主调用线程,然后主调用线程就会从 `await()` 函数返回,继续后余动作。
71+
线程 A 尝试获取锁的过程如下图所示(图源[从 ReentrantLock 的实现看 AQS 的原理及应用 - 美团技术团队](./reentrantlock.md)):
72+
73+
![AQS 独占模式获取锁](https://oss.javaguide.cn/github/javaguide/java/concurrent/aqs-exclusive-mode-acquire-lock.png)
74+
75+
再以倒计时器 `CountDownLatch` 以例,任务分为 N 个子线程去执行,`state` 也初始化为 N(注意 N 要与线程个数一致)。这 N 个子线程开始执行任务,每执行完一个子线程,就调用一次 `countDown()` 方法。该方法会尝试使用 CAS(Compare and Swap) 操作,让 `state` 的值减少 1。当所有的子线程都执行完毕后(即 `state` 的值变为 0),`CountDownLatch` 会调用 `unpark()` 方法,唤醒主线程。这时,主线程就可以从 `await()` 方法(`CountDownLatch` 中的`await()` 方法而非 AQS 中的)返回,继续执行后续的操作。
7276

7377
### AQS 资源共享方式
7478

@@ -118,7 +122,7 @@ protected boolean isHeldExclusively()
118122

119123
Semaphore 的使用简单,我们这里假设有 N(N>5) 个线程来获取 `Semaphore` 中的共享资源,下面的代码表示同一时刻 N 个线程中只有 5 个线程能获取到共享资源,其他线程都会阻塞,只有获取到共享资源的线程才能执行。等到有线程释放了共享资源,其他阻塞的线程才能获取到。
120124

121-
```java 
125+
```java
122126
// 初始共享资源数量
123127
final Semaphore semaphore = new Semaphore(5);
124128
// 获取1个许可
@@ -308,7 +312,7 @@ semaphore.release(5);// 释放5个许可
308312

309313
#### 原理
310314

311-
`CountDownLatch` 是共享锁的一种实现,它默认构造 AQS 的 `state` 值为 `count`。当线程使用 `countDown()` 方法时,其实使用了`tryReleaseShared`方法以 CAS 的操作来减少 `state`,直至 `state` 为 0 。当调用 `await()` 方法的时候,如果 `state` 不为 0,那就证明任务还没有执行完毕,`await()` 方法就会一直阻塞,也就是说 `await()` 方法之后的语句不会被执行。然后,`CountDownLatch` 会自旋 CAS 判断 `state == 0`,如果 `state == 0` 的话,就会释放所有等待的线程,`await()` 方法之后的语句得到执行。
315+
`CountDownLatch` 是共享锁的一种实现,它默认构造 AQS 的 `state` 值为 `count`。当线程使用 `countDown()` 方法时,其实使用了`tryReleaseShared`方法以 CAS 的操作来减少 `state`,直至 `state` 为 0 。当调用 `await()` 方法的时候,如果 `state` 不为 0,那就证明任务还没有执行完毕,`await()` 方法就会一直阻塞,也就是说 `await()` 方法之后的语句不会被执行`main` 线程被加入到等待队列也就是 CLH 队列中了)。然后,`CountDownLatch` 会自旋 CAS 判断 `state == 0`,如果 `state == 0` 的话,就会释放所有等待的线程,`await()` 方法之后的语句得到执行。
312316

313317
#### 实战
314318

@@ -670,3 +674,8 @@ threadnum:8is finish
670674
threadnum:7is finish
671675
......
672676
```
677+
678+
## 参考
679+
680+
- Java 并发之 AQS 详解:<https://www.cnblogs.com/waterystone/p/4920797.html>
681+
- 从 ReentrantLock 的实现看 AQS 的原理及应用:<https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html>

docs/java/concurrent/java-concurrent-questions-02.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ JMM(Java 内存模型)相关的问题比较多,也比较重要,于是我
2222

2323
在 Java 中,`volatile` 关键字可以保证变量的可见性,如果我们将变量声明为 **`volatile`** ,这就指示 JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。
2424

25-
![](https://oss.javaguide.cn/github/javaguide/java/concurrent/jmm.png)
25+
![JMM(Java 内存模型)](https://oss.javaguide.cn/github/javaguide/java/concurrent/jmm.png)
2626

2727
![JMM(Java 内存模型)强制在主存中进行读取](https://oss.javaguide.cn/github/javaguide/java/concurrent/jmm2.png)
2828

0 commit comments

Comments
 (0)