@@ -30,17 +30,17 @@ AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求
30
30
31
31
CLH 锁是对自旋锁的一种改进,是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系),暂时获取不到锁的线程将被加入到该队列中。AQS 将每条请求共享资源的线程封装成一个 CLH 队列锁的一个结点(Node)来实现锁的分配。在 CLH 队列锁中,一个节点表示一个线程,它保存着线程的引用(thread)、 当前节点在队列中的状态(waitStatus)、前驱节点(prev)、后继节点(next)。
32
32
33
- CLH 队列锁结构如下图所示 :
33
+ CLH 队列结构如下图所示 :
34
34
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 )
36
36
37
37
关于 AQS 核心数据结构-CLH 锁的详细解读,强烈推荐阅读 [ Java AQS 核心数据结构-CLH 锁 - Qunar 技术沙龙] ( https://mp.weixin.qq.com/s/jEx-4XhNGOFdCo4Nou5tqg ) 这篇文章。
38
38
39
- AQS(` AbstractQueuedSynchronizer ` )的核心原理图(图源 [ Java 并发之 AQS 详解 ] ( https://www.cnblogs.com/waterystone/p/4920797.html ) )如下 :
39
+ AQS(` AbstractQueuedSynchronizer ` )的核心原理图:
40
40
41
- ![ ] ( https://oss.javaguide.cn/github/javaguide/CLH .png )
41
+ ![ CLH 队列 ] ( https://oss.javaguide.cn/github/javaguide/java/concurrent/clh-queue-state .png )
42
42
43
- AQS 使用 ** int 成员变量 ` state ` 表示同步状态** ,通过内置的 ** 线程等待队列 ** 来完成获取资源线程的排队工作。
43
+ AQS 使用 ** int 成员变量 ` state ` 表示同步状态** ,通过内置的 ** FIFO 线程等待/同步队列 ** 来完成获取资源线程的排队工作。
44
44
45
45
` state ` 变量由 ` volatile ` 修饰,用于展示当前临界资源的获锁情况。
46
46
@@ -66,9 +66,13 @@ protected final boolean compareAndSetState(int expect, int update) {
66
66
}
67
67
```
68
68
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,也就是让锁恢复到未锁定状态。只有这样,其他等待的线程才能有机会获取该锁 。
70
70
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 中的)返回,继续执行后续的操作。
72
76
73
77
### AQS 资源共享方式
74
78
@@ -118,7 +122,7 @@ protected boolean isHeldExclusively()
118
122
119
123
Semaphore 的使用简单,我们这里假设有 N(N>5) 个线程来获取 ` Semaphore ` 中的共享资源,下面的代码表示同一时刻 N 个线程中只有 5 个线程能获取到共享资源,其他线程都会阻塞,只有获取到共享资源的线程才能执行。等到有线程释放了共享资源,其他阻塞的线程才能获取到。
120
124
121
- ``` java
125
+ ``` java
122
126
// 初始共享资源数量
123
127
final Semaphore semaphore = new Semaphore (5 );
124
128
// 获取1个许可
@@ -308,7 +312,7 @@ semaphore.release(5);// 释放5个许可
308
312
309
313
#### 原理
310
314
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() ` 方法之后的语句得到执行。
312
316
313
317
#### 实战
314
318
@@ -670,3 +674,8 @@ threadnum:8is finish
670
674
threadnum:7is finish
671
675
......
672
676
```
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 >
0 commit comments