Skip to content

Commit 42e90b7

Browse files
committed
📝 Writing docs.
1 parent 5a4993c commit 42e90b7

1 file changed

Lines changed: 57 additions & 27 deletions

File tree

docs/concurrent/线程池.md

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,22 @@ tags:
1212

1313
<!-- TOC depthFrom:2 depthTo:4 -->
1414

15-
* [简介](#简介)
15+
* [概述](#概述)
1616
* [什么是线程池?](#什么是线程池)
1717
* [为什么要用线程池?](#为什么要用线程池)
1818
* [Executor 框架](#executor-框架)
19+
* [简介](#简介)
1920
* [ThreadPoolExecutor](#threadpoolexecutor)
20-
* [ThreadPoolExecutor 重要 API](#threadpoolexecutor-重要-api)
21+
* [参数说明](#参数说明)
22+
* [重要方法](#重要方法)
2123
* [向线程池提交任务](#向线程池提交任务)
2224
* [线程池的关闭](#线程池的关闭)
2325
* [Executors](#executors)
2426
* [newCachedThreadPool](#newcachedthreadpool)
2527
* [newFixedThreadPool](#newfixedthreadpool)
2628
* [newSingleThreadExecutor](#newsinglethreadexecutor)
2729
* [newScheduleThreadPool](#newschedulethreadpool)
28-
* [原理](#原理)
30+
* [源码](#源码)
2931
* [线程池状态](#线程池状态)
3032
* [任务的执行](#任务的执行)
3133
* [线程池中的线程初始化](#线程池中的线程初始化)
@@ -37,20 +39,25 @@ tags:
3739

3840
<!-- /TOC -->
3941

40-
## 简介
42+
## 概述
4143

4244
### 什么是线程池?
4345

4446
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。
4547

4648
### 为什么要用线程池?
4749

48-
* 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
49-
* 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
50-
* 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。但是要做到合理的利用线程池,必须对其原理了如指掌。
50+
* 降低资源消耗
51+
* 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
52+
* 提高响应速度
53+
* 当任务到达时,任务可以不需要等到线程创建就能立即执行。
54+
* 提高线程的可管理性
55+
* 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。但是要做到合理的利用线程池,必须对其原理了如指掌。
5156

5257
## Executor 框架
5358

59+
### 简介
60+
5461
<p align="center">
5562
<img src="https://raw.githubusercontent.com/dunwu/javase-notes/master/images/concurrent/Exexctor-uml.png" alt="semaphore">
5663
</p>
@@ -64,7 +71,7 @@ tags:
6471

6572
### ThreadPoolExecutor
6673

67-
`java.uitl.concurrent.ThreadPoolExecutor` 类是 Java 线程池中最核心的一个类
74+
`java.uitl.concurrent.ThreadPoolExecutor` 类是 Executor 框架中最核心的一个类
6875

6976
ThreadPoolExecutor 有四个构造方法,前三个都是基于第四个实现。第四个构造方法定义如下:
7077

@@ -78,32 +85,32 @@ ThreadPoolExecutor 有四个构造方法,前三个都是基于第四个实现
7885
RejectedExecutionHandler handler) {
7986
```
8087

81-
参数说明
88+
#### 参数说明
8289

83-
* corePoolSize(线程池的基本大小):核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了 prestartAllCoreThreads()或者 prestartCoreThread()方法,从这 2 个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建 corePoolSize 个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为 0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到 corePoolSize 后,就会把到达的任务放到缓存队列当中。
84-
* maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。
85-
* keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。
90+
* `corePoolSize`:线程池的基本线程数。这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了 prestartAllCoreThreads()或者 prestartCoreThread()方法,从这 2 个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建 corePoolSize 个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为 0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到 corePoolSize 后,就会把到达的任务放到缓存队列当中。
91+
* `maximumPoolSize`:线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。
92+
* `keepAliveTime`:线程活动保持时间。线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。
8693
* unit:参数 keepAliveTime 的时间单位,有 7 种取值。可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
87-
* workQueue(任务队列):用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列。
94+
* `workQueue`:任务队列。用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列。
8895
* ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
8996
* LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按 FIFO (先进先出) 排序元素,吞吐量通常要高于 ArrayBlockingQueue。静态工厂方法 Executors.newFixedThreadPool()使用了这个队列。
9097
* SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。
9198
* PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
92-
* threadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
93-
* handler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是 AbortPolicy,表示无法处理新任务时抛出异常。以下是 JDK1.5 提供的四种策略。
99+
* `threadFactory`:创建线程的工厂。可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
100+
* `handler`:饱和策略。当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是 AbortPolicy,表示无法处理新任务时抛出异常。以下是 JDK1.5 提供的四种策略。
94101
* AbortPolicy:直接抛出异常。
95102
* CallerRunsPolicy:只用调用者所在线程来运行任务。
96103
* DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
97104
* DiscardPolicy:不处理,丢弃掉。
98105
* 当然也可以根据应用场景需要来实现 RejectedExecutionHandler 接口自定义策略。如记录日志或持久化不能处理的任务。
99106

100-
#### ThreadPoolExecutor 重要 API
107+
#### 重要方法
101108

102109
ThreadPoolExecutor 类中有几个非常重要的方法:
103110

104-
* execute()方法实际上是 Executor 中声明的方法,在 ThreadPoolExecutor 进行了具体的实现,这个方法是 ThreadPoolExecutor 的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
105-
* submit()方法是在 ExecutorService 中声明的方法,在 AbstractExecutorService 就已经有了具体的实现,在 ThreadPoolExecutor 中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和 execute()方法不同,它能够返回任务执行的结果,去看 submit()方法的实现,会发现它实际上还是调用的 execute()方法,只不过它利用了 Future 来获取任务执行结果(Future 相关内容将在下一篇讲述)。
106-
* shutdown()shutdownNow()是用来关闭线程池的。
111+
* `execute()` 方法实际上是 Executor 中声明的方法,在 ThreadPoolExecutor 进行了具体的实现,这个方法是 ThreadPoolExecutor 的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
112+
* `submit()` 方法是在 ExecutorService 中声明的方法,在 AbstractExecutorService 就已经有了具体的实现,在 ThreadPoolExecutor 中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和 execute()方法不同,它能够返回任务执行的结果,去看 submit()方法的实现,会发现它实际上还是调用的 execute()方法,只不过它利用了 Future 来获取任务执行结果(Future 相关内容将在下一篇讲述)。
113+
* `shutdown()` 和 `shutdownNow()` 是用来关闭线程池的。
107114

108115
#### 向线程池提交任务
109116

@@ -156,8 +163,8 @@ JDK 中提供了几种具有代表性的线程池,这些线程池是基于 `Th
156163

157164
这种类型的线程池特点是:
158165

159-
* 工作线程的创建数量几乎没有限制(其实也有限制的,数目为 Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
160-
* 如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为 1 分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
166+
* 工作线程的创建数量几乎没有限制其实也有限制的,数目为 Interger.MAX_VALUE, 这样可灵活的往线程池中添加线程。
167+
* 如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间默认为 1 分钟,则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
161168
* 在使用 CachedThreadPool 时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
162169

163170
示例:
@@ -258,7 +265,7 @@ public class ScheduledThreadPoolDemo {
258265
}
259266
```
260267

261-
## 原理
268+
## 源码
262269

263270
线程池的具体实现原理,大致从以下几个方面讲解:
264271

@@ -312,11 +319,34 @@ TIDYING -> TERMINATED
312319

313320
### 任务的执行
314321

315-
任务执行的核心方法是 execute() 方法。执行步骤如下:
322+
任务执行的核心方法是 `execute()` 方法。执行步骤如下:
323+
324+
1. 如果少于 corePoolSize 个线程正在运行,尝试使用给定命令作为第一个任务启动一个新线程。对 addWorker 的调用会自动检查 runState 和 workerCount,从而防止在不应该的情况下添加线程。
325+
2. 如果任务排队成功,仍然需要仔细检查是否应该添加一个线程(因为现有的线程自上次检查以来已经死亡)或者自从进入方法后,线程池就关闭了。所以我们重新检查状态,如果有必要的话,在线程池停止状态时回滚队列,如果没有线程的话,就开始一个新的线程。
326+
3. 如果任务排队失败,那么我们尝试添加一个新的线程。如果失败了,说明线程池已经关闭了,或者已经饱和了,所以拒绝这个任务。
316327

317-
1. 如果少于 corePoolSize 线程正在运行,请尝试用给定的命令作为第一个启动一个新的线程任务。对 addWorker 的调用会自动检查 runState 和 workerCount,从而防止在不应该的情况下添加线程。
318-
2. 如果任务能够成功排队,那么我们仍然需要仔细检查是否应该添加一个线程(因为现有的自从上次检查以来死亡)或者自从进入方法后,线程池就关闭了。所以我们重新检查状态,如果有必要的话,在线程池停止状态时回滚队列,或者如果没有线程的话就开始一个新的线程。
319-
3. 如果我们不能给任务排队,那么我们尝试添加一个新的线程。如果失败了,我们知道我们已经关闭了,或者已经饱和了,所以拒绝这个任务。
328+
```java
329+
public void execute(Runnable command) {
330+
if (command == null)
331+
throw new NullPointerException();
332+
333+
int c = ctl.get();
334+
if (workerCountOf(c) < corePoolSize) {
335+
if (addWorker(command, true))
336+
return;
337+
c = ctl.get();
338+
}
339+
if (isRunning(c) && workQueue.offer(command)) {
340+
int recheck = ctl.get();
341+
if (! isRunning(recheck) && remove(command))
342+
reject(command);
343+
else if (workerCountOf(recheck) == 0)
344+
addWorker(null, false);
345+
}
346+
else if (!addWorker(command, false))
347+
reject(command);
348+
}
349+
```
320350

321351
### 线程池中的线程初始化
322352

@@ -348,7 +378,7 @@ workQueue 的类型为 BlockingQueue<Runnable>,通常可以取下面三种类
348378

349379
1. ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
350380
2. LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为 Integer.MAX_VALUE
351-
3. synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
381+
3. SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
352382

353383
### 任务拒绝策略
354384

0 commit comments

Comments
 (0)