Skip to content

Commit 98ec9ae

Browse files
committed
[docs update]并发部分内容修改
1 parent d45fdb3 commit 98ec9ae

15 files changed

+56
-49
lines changed

docs/about-the-author/zhishixingqiu-two-years.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ star: 2
4848

4949
### PDF 面试手册
5050

51-
免费赠送多本优质的 PDF 面试手册。
51+
免费赠送多本优质 PDF 面试手册。
5252

5353
![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/xingqiu/image-20220723120918434.png)
5454

docs/java/concurrent/aqs.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ tag:
1717

1818
AQS 的全称为 `AbstractQueuedSynchronizer` ,翻译过来的意思就是抽象队列同步器。这个类在 `java.util.concurrent.locks` 包下面。
1919

20-
![enter image description here](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/AQS.png)
20+
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/AQS.png)
2121

2222
AQS 就是一个抽象类,主要用来构建锁和同步器。
2323

docs/java/concurrent/completablefuture-intro.md

+1-3
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,12 @@ public boolean isDone() {
7575
}
7676
```
7777

78-
获取异步计算的结果也非常简单,直接调用 `get()` 方法即可
78+
获取异步计算的结果也非常简单,直接调用 `get()` 方法即可。调用 `get()` 方法的线程会阻塞直到 `CompletableFuture` 完成运算。
7979

8080
```java
8181
rpcResponse = completableFuture.get();
8282
```
8383

84-
注意 : `get()` 方法并不会阻塞,因为我们已经知道异步运算的结果了。
85-
8684
如果你已经知道计算的结果的话,可以使用静态方法 `completedFuture()` 来创建 `CompletableFuture`
8785

8886
```java
-279 Bytes
Loading
-135 Bytes
Loading
-50 Bytes
Loading
-38 Bytes
Loading
-107 Bytes
Loading
-618 Bytes
Loading
-573 Bytes
Loading
-247 Bytes
Loading
-31 Bytes
Loading

docs/java/concurrent/java-thread-pool-best-practices.md

+32-22
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ tag:
99

1010
## 线程池知识回顾
1111

12-
开始这篇文章之前还是简单介绍一嘴线程池,之前写的[《新手也能看懂的线程池学习总结》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485808&idx=1&sn=1013253533d73450cef673aee13267ab&chksm=cea246bbf9d5cfad1c21316340a0ef1609a7457fea4113a1f8d69e8c91e7d9cd6285f5ee1490&token=510053261&lang=zh_CN&scene=21#wechat_redirect)这篇文章介绍的很详细了。
12+
开始这篇文章之前还是简单介绍线程池,之前写的[《新手也能看懂的线程池学习总结》](./java-thread-pool-summary.md)这篇文章介绍的很详细了。
1313

1414
### 为什么要使用线程池?
1515

@@ -29,7 +29,7 @@ tag:
2929

3030
假设我们要执行三个不相关的耗时任务,Guide 画图给大家展示了使用线程池前后的区别。
3131

32-
注意:**下面三个任务可能做的是同一件事情,也可能是不一样的事情。**
32+
注意:**下面三个任务可能做的是同一件事情,也可能是不一样的事情。**
3333

3434
> 使用多线程前应为:任务 1 --> 任务 2 --> 任务 3(图中把任务 3 画错为 任务 2)
3535
@@ -39,7 +39,7 @@ tag:
3939

4040
一般是通过 `ThreadPoolExecutor` 的构造函数来创建线程池,然后提交任务给线程池执行就可以了。
4141

42-
`ThreadPoolExecutor`构造函数如下:
42+
`ThreadPoolExecutor`构造函数如下:
4343

4444
```java
4545
/**
@@ -130,11 +130,11 @@ Finished all threads
130130

131131
简单总结一下我了解的使用线程池的时候应该注意的东西,网上似乎还没有专门写这方面的文章。
132132

133-
因为Guide还比较菜,有补充和完善的地方,可以在评论区告知或者在微信上与我交流。
133+
因为 Guide 还比较菜,有补充和完善的地方,可以在评论区告知或者在微信上与我交流。
134134

135-
### 1. 使用 `ThreadPoolExecutor ` 的构造函数声明线程池
135+
### 1. 使用 `ThreadPoolExecutor` 的构造函数声明线程池
136136

137-
**1. 线程池必须手动通过 `ThreadPoolExecutor ` 的构造函数来声明,避免使用`Executors ` 类的 `newFixedThreadPool``newCachedThreadPool` ,因为可能会有 OOM 的风险。**
137+
**1. 线程池必须手动通过 `ThreadPoolExecutor` 的构造函数来声明,避免使用`Executors` 类的 `newFixedThreadPool``newCachedThreadPool` ,因为可能会有 OOM 的风险。**
138138

139139
> Executors 返回线程池对象的弊端如下:
140140
>
@@ -143,7 +143,7 @@ Finished all threads
143143
144144
说白了就是:**使用有界队列,控制线程创建数量。**
145145

146-
除了避免 OOM 的原因之外,不推荐使用 `Executors `提供的两种快捷的线程池的原因还有:
146+
除了避免 OOM 的原因之外,不推荐使用 `Executors`提供的两种快捷的线程池的原因还有:
147147

148148
1. 实际使用中需要根据自己机器的性能、业务场景来手动配置线程池的参数比如核心线程数、使用的任务队列、饱和策略等等。
149149
2. 我们应该显示地给我们的线程池命名,这样有助于我们定位问题。
@@ -152,7 +152,7 @@ Finished all threads
152152

153153
你可以通过一些手段来检测线程池的运行状态比如 SpringBoot 中的 Actuator 组件。
154154

155-
除此之外,我们还可以利用 `ThreadPoolExecutor` 的相关 API做一个简陋的监控。从下图可以看出, `ThreadPoolExecutor`提供了获取线程池当前的线程数和活跃线程数、已经执行完成的任务数、正在排队中的任务数等等。
155+
除此之外,我们还可以利用 `ThreadPoolExecutor` 的相关 API 做一个简陋的监控。从下图可以看出, `ThreadPoolExecutor`提供了获取线程池当前的线程数和活跃线程数、已经执行完成的任务数、正在排队中的任务数等等。
156156

157157
![](./images/thread-pool/ddf22709-bff5-45b4-acb7-a3f2e6798608.png)
158158

@@ -235,7 +235,7 @@ public final class NamingThreadFactory implements ThreadFactory {
235235
this.name = name; // TODO consider uniquifying this
236236
}
237237

238-
@Override
238+
@Override
239239
public Thread newThread(Runnable r) {
240240
Thread t = delegate.newThread(r);
241241
t.setName(name + " [#" + threadNum.incrementAndGet() + "]");
@@ -263,24 +263,37 @@ public final class NamingThreadFactory implements ThreadFactory {
263263
>
264264
> Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
265265
266-
**类比于实现世界中的人类通过合作做某件事情,我们可以肯定的一点是线程池大小设置过大或者过小都会有问题,合适的才是最好。**
266+
类比于实现世界中的人类通过合作做某件事情,我们可以肯定的一点是线程池大小设置过大或者过小都会有问题,合适的才是最好。
267267

268-
**如果我们设置的线程池数量太小的话,如果同一时间有大量任务/请求需要处理,可能会导致大量的请求/任务在任务队列中排队等待执行,甚至会出现任务队列满了之后任务/请求无法处理的情况,或者大量任务堆积在任务队列导致 OOM。这样很明显是有问题的! CPU 根本没有得到充分利用。**
269-
270-
**但是,如果我们设置线程数量太大,大量线程可能会同时在争取 CPU 资源,这样会导致大量的上下文切换,从而增加线程的执行时间,影响了整体执行效率。**
268+
- 如果我们设置的线程池数量太小的话,如果同一时间有大量任务/请求需要处理,可能会导致大量的请求/任务在任务队列中排队等待执行,甚至会出现任务队列满了之后任务/请求无法处理的情况,或者大量任务堆积在任务队列导致 OOM。这样很明显是有问题的,CPU 根本没有得到充分利用。
269+
- 如果我们设置线程数量太大,大量线程可能会同时在争取 CPU 资源,这样会导致大量的上下文切换,从而增加线程的执行时间,影响了整体执行效率。
271270

272271
有一个简单并且适用面比较广的公式:
273272

274-
- **CPU 密集型任务(N+1):** 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
273+
- **CPU 密集型任务(N+1):** 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
275274
- **I/O 密集型任务(2N):** 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。
276275

277276
**如何判断是 CPU 密集任务还是 IO 密集任务?**
278277

279278
CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。但凡涉及到网络读取,文件读取这类都是 IO 密集型,这类任务的特点是 CPU 计算耗费时间相比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上。
280279

280+
> 🌈 拓展一下(参见:[issue#1737](https://github.com/Snailclimb/JavaGuide/issues/1737)):
281+
>
282+
> 线程数更严谨的计算的方法应该是:`最佳线程数 = N(CPU 核心数)∗(1+WT(线程等待时间)/ST(线程计算时间))`,其中 `WT(线程等待时间)=线程运行总时间 - ST(线程计算时间)`
283+
>
284+
> 线程等待时间所占比例越高,需要越多线程。线程计算时间所占比例越高,需要越少线程。
285+
>
286+
> 我们可以通过 JDK 自带的工具 VisualVM 来查看 `WT/ST` 比例。
287+
>
288+
> CPU 密集型任务的 `WT/ST` 接近或者等于 0,因此, 线程数可以设置为 N(CPU 核心数)∗(1+0)= N,和我们上面说的 N(CPU 核心数)+1 差不多。
289+
>
290+
> IO 密集型任务下,几乎全是线程等待时间,从理论上来说,你就可以将线程数设置为 2N(按道理来说,WT/ST 的结果应该比较大,这里选择 2N 的原因应该是为了避免创建过多线程吧)。
291+
292+
**公示也只是参考,具体还是要根据项目实际线上运行情况来动态调整。我在后面介绍的美团的线程池参数动态配置这种方案就非常不错,很实用!**
293+
281294
#### 美团的骚操作
282295

283-
美团技术团队在[Java线程池实现原理及其在美团业务中的实践](https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html)这篇文章中介绍到对线程池参数实现可自定义配置的思路和方法。
296+
美团技术团队在[Java 线程池实现原理及其在美团业务中的实践](https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html)这篇文章中介绍到对线程池参数实现可自定义配置的思路和方法。
284297

285298
美团技术团队的思路是主要对线程池的核心参数实现自定义可配置。这三个核心参数是:
286299

@@ -290,21 +303,18 @@ CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内
290303

291304
**为什么是这三个参数?**
292305

293-
我在这篇[《新手也能看懂的线程池学习总结》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485808&idx=1&sn=1013253533d73450cef673aee13267ab&chksm=cea246bbf9d5cfad1c21316340a0ef1609a7457fea4113a1f8d69e8c91e7d9cd6285f5ee1490&token=510053261&lang=zh_CN&scene=21#wechat_redirect) 中就说过这三个参数是 `ThreadPoolExecutor` 最重要的参数,它们基本决定了线程池对于任务的处理策略。
306+
我在这篇[《新手也能看懂的线程池学习总结》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485808&idx=1&sn=1013253533d73450cef673aee13267ab&chksm=cea246bbf9d5cfad1c21316340a0ef1609a7457fea4113a1f8d69e8c91e7d9cd6285f5ee1490&token=510053261&lang=zh_CN&scene=21#wechat_redirect) 中就说过这三个参数是 `ThreadPoolExecutor` 最重要的参数,它们基本决定了线程池对于任务的处理策略。
294307

295308
**如何支持参数动态配置?** 且看 `ThreadPoolExecutor` 提供的下面这些方法。
296309

297310
![](./images/thread-pool/b6fd95a7-4c9d-4fc6-ad26-890adb3f6c4c.png)
298311

299-
格外需要注意的是`corePoolSize` 程序运行期间的时候,我们调用 `setCorePoolSize() `这个方法的话,线程池会首先判断当前工作线程数是否大于`corePoolSize`,如果大于的话就会回收工作线程。
312+
格外需要注意的是`corePoolSize`, 程序运行期间的时候,我们调用 `setCorePoolSize()`这个方法的话,线程池会首先判断当前工作线程数是否大于`corePoolSize`,如果大于的话就会回收工作线程。
300313

301-
另外,你也看到了上面并没有动态指定队列长度的方法,美团的方式是自定义了一个叫做 `ResizableCapacityLinkedBlockIngQueue` 的队列(主要就是把`LinkedBlockingQueue`的capacity 字段的final关键字修饰给去掉了,让它变为可变的)。
314+
另外,你也看到了上面并没有动态指定队列长度的方法,美团的方式是自定义了一个叫做 `ResizableCapacityLinkedBlockIngQueue` 的队列(主要就是把`LinkedBlockingQueue`的 capacity 字段的 final 关键字修饰给去掉了,让它变为可变的)。
302315

303316
最终实现的可动态修改线程池参数效果如下。👏👏👏
304317

305318
![动态配置线程池参数最终效果](./images/thread-pool/19a0255a-6ef3-4835-98d1-a839d1983332.png)
306319

307-
还没看够?推荐 why神的[《如何设置线程池参数?美团给出了一个让面试官虎躯一震的回答。》](https://mp.weixin.qq.com/s/9HLuPcoWmTqAeFKa1kj-_A)这篇文章,深度剖析,很不错哦!
308-
309-
310-
320+
还没看够?推荐 why 神的[《如何设置线程池参数?美团给出了一个让面试官虎躯一震的回答。》](https://mp.weixin.qq.com/s/9HLuPcoWmTqAeFKa1kj-_A)这篇文章,深度剖析,很不错哦!

docs/java/concurrent/reentrantlock.md

+19-20
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ static final class NonfairSync extends Sync {
9696

9797
带着非公平锁的这些问题,再看下公平锁源码中获锁的方式:
9898

99-
```
99+
```java
100100
// java.util.concurrent.locks.ReentrantLock#FairSync
101101

102102
static final class FairSync extends Sync {
@@ -218,32 +218,31 @@ private volatile int state;
218218

219219
![](https://p1.meituan.net/travelcube/b8b53a70984668bc68653efe9531573e78636.png)
220220

221-
> 🐛 修正: 图中的一处小错误,(AQS)CAS修改共享资源 State 成功之后应该是获取锁成功(非公平锁)。
221+
> 🐛 修正(参见: [issue#1761](https://github.com/Snailclimb/JavaGuide/issues/1761): 图中的一处小错误,(AQS)CAS修改共享资源 State 成功之后应该是获取锁成功(非公平锁)。
222222
>
223223
> 对应的源码如下:
224224
>
225225
> ```java
226226
> final boolean nonfairTryAcquire(int acquires) {
227-
> final Thread current = Thread.currentThread();//获取当前线程
228-
> int c = getState();
229-
> if (c == 0) {
230-
> if (compareAndSetState(0, acquires)) {//CAS抢锁
231-
> setExclusiveOwnerThread(current);//设置当前线程为独占线程
232-
> return true;//抢锁成功
233-
> }
234-
> }
235-
> else if (current == getExclusiveOwnerThread()) {
236-
> int nextc = c + acquires;
237-
> if (nextc < 0) // overflow
238-
> throw new Error("Maximum lock count exceeded");
239-
> setState(nextc);
240-
> return true;
241-
> }
242-
> return false;
243-
> }
227+
> final Thread current = Thread.currentThread();//获取当前线程
228+
> int c = getState();
229+
> if (c == 0) {
230+
> if (compareAndSetState(0, acquires)) {//CAS抢锁
231+
> setExclusiveOwnerThread(current);//设置当前线程为独占线程
232+
> return true;//抢锁成功
233+
> }
234+
> }
235+
> else if (current == getExclusiveOwnerThread()) {
236+
> int nextc = c + acquires;
237+
> if (nextc < 0) // overflow
238+
> throw new Error("Maximum lock count exceeded");
239+
> setState(nextc);
240+
> return true;
241+
> }
242+
> return false;
243+
> }
244244
> ```
245245
>
246-
>
247246
248247
为了帮助大家理解 ReentrantLockAQS 之间方法的交互过程,以非公平锁为例,我们将加锁和解锁的交互流程单独拎出来强调一下,以便于对后续内容的理解。
249248

docs/java/concurrent/threadlocal.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ new ThreadLocal<>().set(s);
170170

171171
其实是不对的,因为题目说的是在做 `ThreadLocal.get()` 操作,证明其实还是有**强引用**存在的,所以 `key` 并不为 `null`,如下图所示,`ThreadLocal`的**强引用**仍然是存在的。
172172

173-
![image.png](./images/thread-local/5.png)
173+
![](./images/thread-local/5.png)
174174

175175
如果我们的**强引用**不存在的话,那么 `key` 就会被回收,也就是会出现我们 `value` 没被回收,`key` 被回收,导致 `value` 永远存在,出现内存泄漏。
176176

@@ -694,7 +694,7 @@ private void resize() {
694694

695695
我们以`get(ThreadLocal1)`为例,通过`hash`计算后,正确的`slot`位置应该是 4,而`index=4`的槽位已经有了数据,且`key`值不等于`ThreadLocal1`,所以需要继续往后迭代查找。
696696

697-
迭代到`index=5`的数据时,此时`Entry.key=null`,触发一次探测式数据回收操作,执行`expungeStaleEntry()`方法,执行完后,`index 5,8`的数据都会被回收,而`index 6,7`的数据都会前移,此时继续往后迭代,到`index = 6`的时候即找到了`key`值相等的`Entry`数据,如下图所示:
697+
迭代到`index=5`的数据时,此时`Entry.key=null`,触发一次探测式数据回收操作,执行`expungeStaleEntry()`方法,执行完后,`index 5,8`的数据都会被回收,而`index 6,7`的数据都会前移`index 6,7`前移之后,继续从 `index=5` 往后迭代,于是就在 `index=5` 找到了`key`值相等的`Entry`数据,如下图所示:
698698

699699
![](./images/thread-local/28.png)
700700

0 commit comments

Comments
 (0)