ThreadPoolExecutor、任务队列与拒绝策略 | JavaSE
ThreadPoolExecutor、任务队列与拒绝策略
一、学习目标
完成本章后,你应该能够:
- 独立写出
ThreadPoolExecutor的核心七参数构造方式。 - 准确解释
corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。 - 理解“核心线程数”和“最大线程数”之间的关系。
- 理解教材所谓“临时线程”在 ThreadPoolExecutor 中真正指什么。
- 完整说出一个新任务提交到线程池后的处理顺序。
- 准确判断什么时候进入任务队列、什么时候创建额外工作线程、什么时候发生拒绝。
- 理解有界队列、无界队列和直接移交队列的基本差异。
- 掌握
ArrayBlockingQueue在线程池中的基本用途。 - 掌握 JDK 提供的四种标准拒绝策略。
- 准确理解
CallerRunsPolicy中的“Caller”是谁,而不是机械理解成 main 线程。 - 能够根据一组具体线程池参数推导任务 1、2、3……分别去哪。
- 理解为什么线程池配置本质上是在平衡吞吐量、延迟和资源上限。
- 能够识别常见线程池参数配置风险。
二、核心知识
2.1 ThreadPoolExecutor 是什么
ThreadPoolExecutor 是 Java 中经典的:
可配置线程池实现类。
上一章:
ExecutorService pool =
Executors.newFixedThreadPool(3);
是使用一个预配置方案。
这一章我们直接:
ExecutorService pool =
new ThreadPoolExecutor(...);
自己决定:
核心线程数
最大线程数
空闲回收时间
任务队列
线程工厂
拒绝策略
这意味着我们终于能够回答:
当线程全忙了以后,到底发生什么?
2.2 七参数构造器
原课程主线使用:
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
例如:
ExecutorService pool =
new ThreadPoolExecutor(
3,
5,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
可以把它理解为:
正式员工:3人
最大员工:5人
最多临时增加:2人
临时人员工空闲10秒后可被回收
等待区容量:3
HR:ThreadFactory
客满以后的处理规则:
CallerRunsPolicy
这个“餐厅/公司”模型非常适合入门。
三、七个参数详解
3.1 corePoolSize:核心线程数量
corePoolSize = 3
表示:
线程池的核心规模 = 3
当任务到来,并且当前运行的工作线程数量小于:
3
ThreadPoolExecutor 会优先创建新的工作线程处理任务。
例如:
corePoolSize = 3
任务1:
创建线程1
任务2:
创建线程2
任务3:
创建线程3
所以:
前3个任务
会逐步建立核心规模的工作线程。
3.2 核心线程是不是线程池创建时就一定已经存在?
不一定。
默认情况下,核心工作线程也是:
新任务到来时
↓
按需创建
而不是:
new ThreadPoolExecutor()
↓
立即自动创建全部corePoolSize线程
如果确实希望提前启动核心线程,可以使用:
prestartCoreThread();
或:
prestartAllCoreThreads();
但这属于补充能力。
当前最重要的是:
corePoolSize = 3表示核心线程规模,不表示构造器执行瞬间一定已经存在3条线程。
3.3 maximumPoolSize:最大线程数量
例如:
maximumPoolSize = 5
表示线程池中的工作线程数:
最多允许达到5
而:
corePoolSize = 3
maximumPoolSize = 5
意味着:
核心规模:3
最多额外扩展:
5 - 3 = 2
教材经常把超过核心数量的部分叫:
临时线程
例如:
线程1
线程2
线程3
→ 核心规模
线程4
线程5
→ 压力高时额外创建
3.4 “临时线程”是教学概念
需要准确理解:
ThreadPoolExecutor API 并没有定义一个:
TemporaryThread
类型。
所谓:
临时线程
指的是:
当前线程池规模超过
corePoolSize后,为处理压力而额外存在的工作线程。
所以正式术语仍然是:
worker thread
工作线程
只不过在解释:
corePoolSize
maximumPoolSize
时,把超过核心规模的线程称为:
临时线程
更容易入门。
3.5 keepAliveTime
例如:
keepAliveTime = 10
unit = TimeUnit.SECONDS
共同表示:
超过核心规模的工作线程在空闲达到指定时间后,可以被回收。
例如:
core = 3
max = 5
高峰期:
5条线程
任务减少以后:
线程4空闲
线程5空闲
持续:
10秒
以后,它们可能被终止。
最终线程池重新收缩到:
核心规模附近
3.6 keepAliveTime 默认主要影响谁
默认情况下,keep-alive 策略主要应用于:
超过 corePoolSize 的工作线程
核心线程通常不会仅仅因为空闲超过该时间就自动退出。
ThreadPoolExecutor 也提供:
allowCoreThreadTimeOut(true);
让核心线程也允许超时回收。
但这是额外配置。
本章基本模型仍然是:
核心线程
→ 长期保留
超过核心规模的线程
→ 空闲过久可以回收
3.7 unit:时间单位
keepAliveTime 只是数字:
10
必须配合:
TimeUnit unit
才有完整含义。
例如:
10, TimeUnit.SECONDS
代表:
10秒
而:
10, TimeUnit.MINUTES
代表:
10分钟
常见单位:
TimeUnit.SECONDS
TimeUnit.MILLISECONDS
TimeUnit.MINUTES
TimeUnit.HOURS
3.8 workQueue:任务队列
例如:
new ArrayBlockingQueue<>(3)
表示:
任务等待队列容量 = 3
当核心规模已经达到:
corePoolSize
并且这些工作线程没有立即处理新任务时,新任务通常优先:
进入任务队列
而不是立刻创建超过核心规模的新线程。
这是 ThreadPoolExecutor 最重要的执行规则之一。
3.9 threadFactory:线程工厂
例如:
Executors.defaultThreadFactory()
线程池自己不会凭空拥有 Thread。
当需要新工作线程时,会通过:
ThreadFactory
创建线程。
可以把它理解成:
线程工厂
=
负责“招聘线程”的HR
默认线程工厂会创建普通平台线程。
实际工程中还可以自定义 ThreadFactory:
设置线程名称
设置异常处理
调整线程属性
例如线程名称:
order-pool-1
order-pool-2
比:
pool-3-thread-7
更方便日志诊断。
3.10 handler:拒绝策略
类型:
RejectedExecutionHandler
作用:
当线程池无法接受一个新任务时,决定如何处理这个任务。
例如:
工作线程全部达到最大数量
+
任务队列也满
此时再来一个任务:
怎么办?
答案就在:
拒绝策略
四、任务进入 ThreadPoolExecutor 的完整流程
这是整章最核心的内容。
假设:
corePoolSize = 3
maximumPoolSize = 5
queue capacity = 3
可以先记住一句话:
先核心线程,再队列,再扩到最大线程,最后拒绝。
完整流程:
提交新任务
│
▼
当前线程数 < corePoolSize?
│
├── 是
│ ↓
│ 创建新工作线程
│ 直接处理任务
│
└── 否
↓
尝试进入任务队列
│
├── 队列没满
│ ↓
│ 任务排队
│
└── 队列满
↓
当前线程数 < maximumPoolSize?
│
├── 是
│ ↓
│ 创建额外工作线程
│ 直接处理任务
│
└── 否
↓
拒绝任务
简化:
1. core
2. queue
3. max
4. reject
这是必须闭卷记住的顺序。
五、用具体任务推演一次
线程池:
new ThreadPoolExecutor(
3,
5,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
假设所有任务执行时间很长,提交过程中前面的任务都还没有结束。
5.1 任务1
当前:
线程数 = 0
core = 3
因为:
0 < 3
所以:
创建线程1
↓
执行任务1
5.2 任务2
当前线程数 = 1
1 < 3
所以:
创建线程2
↓
执行任务2
5.3 任务3
当前线程数 = 2
2 < 3
所以:
创建线程3
↓
执行任务3
此时:
核心规模已达到3
5.4 任务4
核心线程都在忙。
线程池不会马上创建:
线程4
而是先:
尝试入队
队列:
[任务4]
5.5 任务5
[任务4, 任务5]
5.6 任务6
[任务4, 任务5, 任务6]
队列达到容量:
3
5.7 任务7
现在:
核心线程全忙
+
队列已满
但是:
当前线程数 = 3
maximumPoolSize = 5
所以可以扩容。
创建:
线程4
直接执行:
任务7
5.8 任务8
同理:
当前线程数 = 4
4 < 5
创建:
线程5
执行:
任务8
5.9 任务9
此时:
线程数 = 5
达到maximumPoolSize
队列 = 3
也已满
所有容量都耗尽。
因此:
任务9
↓
进入拒绝策略
所以本例容量模型是:
直接执行:
任务1、2、3
队列:
任务4、5、6
额外线程直接执行:
任务7、8
任务9:
拒绝
当然这是假设:
前面的任务在提交过程中一直没有完成。
真实程序中如果某些任务已经执行结束,结果就可能变化。
六、什么时候才创建超过核心规模的线程
这是高频易错题。
错误理解:
核心线程都忙了,马上创建临时线程。
不准确。
正确顺序:
核心线程都忙
↓
先尝试把任务加入队列
只有:
队列也无法继续接收
并且:
当前线程数量仍小于maximumPoolSize
才会:
创建超过核心规模的工作线程
也就是教材所称:
临时线程
所以:
核心忙 + 队列满 + 尚未达到最大线程数 → 才扩容。
七、什么时候拒绝任务
典型饱和拒绝条件:
线程数量达到 maximumPoolSize
+
任务队列无法再接收任务
又来了新任务。
那么:
RejectedExecutionHandler
开始工作。
除此之外还有一个非常重要的情况:
线程池已经 shutdown 后,再提交新任务,同样会被拒绝。
所以不能只背:
线程满 + 队列满
还要知道:
Executor已经关闭
也不能接受任务。
八、任务队列
8.1 BlockingQueue
ThreadPoolExecutor 的:
BlockingQueue<Runnable> workQueue
保存的是:
等待线程池执行的任务
不是:
Thread对象
这一点非常重要:
线程池里管理线程
任务队列里放Runnable任务
8.2 ArrayBlockingQueue
原课程示例:
new ArrayBlockingQueue<>(3)
它是:
有界阻塞队列
容量明确:
3
优点:
任务排队数量有清晰上限
因此:
线程数量有限
+
队列容量有限
系统的任务承载边界更加明确。
当全部达到上限:
触发拒绝策略
8.3 有界队列为什么重要
如果队列无限增长:
任务不断进入
↓
处理速度跟不上
↓
等待任务越来越多
虽然线程数没有爆炸:
队列本身也可能消耗大量内存
所以:
限制线程数
只是资源治理的一部分。
还需要考虑:
限制队列容量
8.4 无界队列
例如:
new LinkedBlockingQueue<>()
如果没有人为指定较小容量,可形成非常大的等待空间。
ThreadPoolExecutor 使用这种队列时:
核心线程达到corePoolSize
↓
后续任务持续进入队列
因为队列通常一直可以接收:
就不会因为“队列满”触发扩展到maximumPoolSize
也就是说:
maximumPoolSize
在这种典型配置中可能基本发挥不了扩容作用。
风险:
任务提交速度长期大于消费速度
↓
队列不断增长
↓
内存压力
8.5 SynchronousQueue
还有一种非常特殊的队列:
SynchronousQueue
它并不是普通意义上:
存很多等待任务
而更像:
直接把任务交给一个可用工作线程
如果没有线程可以立即接手:
入队就无法像普通队列那样成功
于是 ThreadPoolExecutor 可能:
尝试创建新线程
这是一种:
直接移交(Direct Handoff)
策略。
本阶段了解即可。
九、四种标准拒绝策略
9.1 AbortPolicy
new ThreadPoolExecutor.AbortPolicy()
这是默认拒绝策略。
行为:
任务无法接受
↓
抛出 RejectedExecutionException
优点:
问题不会悄悄消失
调用方能够知道任务提交失败
例如:
try {
pool.execute(task);
} catch (RejectedExecutionException e) {
System.out.println(
"任务被拒绝"
);
}
适合:
不允许任务静默丢失
的场景。
9.2 DiscardPolicy
new ThreadPoolExecutor.DiscardPolicy()
行为:
任务无法接受
↓
直接丢弃
↓
不主动抛异常
最大问题:
任务可能悄悄消失
例如:
订单任务
支付任务
数据保存任务
显然不能随便使用这种策略。
它只适合:
任务本身确实允许丢弃
的少数场景。
9.3 DiscardOldestPolicy
new ThreadPoolExecutor.DiscardOldestPolicy()
基本行为:
线程池饱和
↓
丢弃队列头部等待最久的任务
↓
重新尝试提交当前新任务
例如:
队列:
A B C
新任务D到来
可能:
丢弃A
↓
重新尝试提交D
注意:
它并不是简单地“删一个旧任务,然后一定成功”。
重新提交仍然可能再次遇到饱和情况。
而且:
默默丢掉一个已经等待很久的任务
通常风险很大。
9.4 CallerRunsPolicy
new ThreadPoolExecutor.CallerRunsPolicy()
这是非常有意思的策略。
当线程池无法执行新任务时:
由调用
execute()的那个线程直接执行任务的run()。
假设:
main线程
↓
pool.execute(task)
线程池饱和。
那么:
Caller = main
此时 main 可能执行任务。
但是如果:
HTTP请求线程
↓
pool.execute(task)
那么 Caller 就可能是:
HTTP请求线程
而不是:
main
因此必须记:
CallerRunsPolicy
=
调用 execute 的线程执行
不能机械记成:
主线程执行
9.5 CallerRunsPolicy 为什么有“反馈控制”效果
假设提交任务的线程:
一直疯狂向线程池提交任务
线程池满了以后:
CallerRunsPolicy
↓
提交者自己被迫执行任务
于是提交线程暂时不能:
继续高速提交
形成:
线程池越忙
↓
提交者越可能自己处理
↓
提交速度自动下降
这是一种简单的:
反压 / 反馈控制思想
9.6 四种策略对比
| 策略 | 行为 |
| --------------------- | -------------------------------------- |
| AbortPolicy | 拒绝并抛 RejectedExecutionException |
| DiscardPolicy | 静默丢弃当前任务 |
| DiscardOldestPolicy | 丢弃队列中最旧等待任务,再重试当前任务 |
| CallerRunsPolicy | 由调用 execute() 的线程执行任务 |
最不能死记硬背的是:
CallerRunsPolicy
因为它不是:
永远main执行
而是:
谁提交
谁可能执行
十、自定义拒绝策略
因为:
RejectedExecutionHandler
本身是接口。
所以也可以自定义:
RejectedExecutionHandler handler =
(task, executor) -> {
System.out.println(
"任务被拒绝:" + task
);
};
然后:
new ThreadPoolExecutor(
...,
handler
);
实际工程可能需要:
记录日志
报警
写入备用队列
降级
返回错误
而不是简单使用四种标准策略。
本阶段知道:
拒绝策略可扩展
即可。
十一、完整线程池示例
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
ExecutorService pool =
new ThreadPoolExecutor(
3,
5,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
for (int i = 1; i <= 10; i++) {
int taskId = i;
pool.execute(() -> {
String name =
Thread.currentThread()
.getName();
System.out.println(
name
+ "正在执行任务"
+ taskId
);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread()
.interrupt();
}
});
}
pool.shutdown();
}
}
这个例子可以用于观察:
核心线程
任务排队
最大线程扩容
CallerRunsPolicy
但实际哪一个具体任务最终走到哪条线程,受到:
任务执行速度
线程调度
提交时机
影响。
不要根据某一次运行输出推断绝对固定任务编号。
十二、原理与进阶
12.1 为什么线程池不是“线程越多越好”
假设:
CPU只有8个核心
却创建:
10000个平台线程
如果它们都是 CPU 密集任务:
大家都在抢CPU
可能导致:
大量上下文切换
调度成本
缓存局部性下降
线程数量增加不再带来性能提升。
12.2 CPU 密集型任务
CPU 密集型(CPU-bound)任务主要耗费:
计算能力
例如:
大量数学计算
编码
复杂算法
图像处理
这种任务线程数一般不能无限增加。
因为:
真正的稀缺资源是CPU核心
12.3 I/O 密集型任务
I/O 密集型(I/O-bound)任务存在大量:
网络等待
文件等待
数据库等待
线程可能长期:
不占用CPU计算
因此经典平台线程模型下,有时可以允许比 CPU 核心数更多的工作线程。
但是究竟设置多少:
没有一个适用于所有系统的固定公式
要结合:
任务特征
响应时间
吞吐量
CPU
内存
外部依赖
实际压测
确定。
所以不要背:
CPU核数 × 2
然后把它当成永恒真理。
12.4 队列越大也不是越好
大队列:
优点:
不容易马上拒绝任务
但:
缺点:
任务可能排很久
内存占用增加
请求延迟越来越高
所以:
“没有拒绝”
并不意味着:
“系统健康”
有时一个请求:
排队5分钟以后才执行
虽然没有被拒绝,业务上仍然已经失败。
12.5 maximumPoolSize 越大也不是越好
最大线程数越大:
能暂时吞下更多并发任务
但同时也意味着可能消耗:
更多内存
更多系统线程
更多调度成本
更多下游连接
甚至:
把数据库先打死
例如:
线程池1000线程
↓
1000个任务同时请求数据库
↓
数据库连接池只有50
最终并不会凭空提升系统性能。
12.6 线程池配置实际上是容量设计
所以 ThreadPoolExecutor 七参数背后的真正问题不是:
“面试的时候怎么把七参数背出来?”
而是:
系统最多允许多少任务同时执行?
↓
多少任务允许等待?
↓
最多等待多久?
↓
超过容量怎么办?
因此线程池是:
资源治理与容量控制工具。
十三、Executors 与 ThreadPoolExecutor 的配置认识
上一章学习:
Executors.newFixedThreadPool(n)
JDK 21 官方说明它使用:
固定数量工作线程
+
共享无界队列
这意味着:
线程数有界
但:
等待任务数量没有较小的明确上限
如果生产速度长期大于消费速度:
任务可以持续堆积
所以在需要:
明确容量边界
的系统中,自行配置:
ThreadPoolExecutor
和:
ArrayBlockingQueue
能够让:
最大线程数
+
最大排队任务数
+
拒绝策略
更加显式。
但这不是说:
Executors API 错了
而是:
预配置线程池是否合适,要根据任务负载模型决定。
十四、常见问题
14.1 corePoolSize=3,构造线程池时就一定有3条线程吗?
默认不一定。
通常在任务到来时按需创建。
14.2 核心线程都忙了以后是不是马上创建临时线程?
不是。
先:
尝试入队
队列也无法接收以后,才考虑扩到:
maximumPoolSize
14.3 maximumPoolSize=5,是不是永远有5条线程?
不是。
它表示:
最大允许数量
不是固定数量。
14.4 keepAliveTime 会不会默认把核心线程也销毁?
默认主要针对:
超过 corePoolSize 的空闲线程
核心线程可以通过额外配置允许超时。
14.5 workQueue 里面放的是 Thread 吗?
不是。
放的是:
等待执行的 Runnable 任务
14.6 队列没满时,会创建超过核心规模的线程吗?
按照 ThreadPoolExecutor 的典型执行规则:
核心规模达到以后
↓
优先入队
因此只有队列无法接收任务以后,才尝试扩展线程数量。
14.7 队列满了就一定拒绝吗?
不一定。
如果:
当前线程数 < maximumPoolSize
还可以:
创建额外工作线程
14.8 什么情况下才典型地进入拒绝策略?
线程已达到maximumPoolSize
+
队列无法再接收
或者:
线程池已经关闭
14.9 CallerRunsPolicy 是 main 线程执行吗?
不一定。
准确说:
调用
execute()的线程执行。
main 提交:
main执行
其他业务线程提交:
那个业务线程执行
14.10 DiscardOldestPolicy 是不是推荐策略?
不能因为名字看起来合理就随意使用。
它会:
丢弃已经在队列中等待的任务
这可能导致严重业务问题。
14.11 队列越大是不是越安全?
不是。
大队列会降低立即拒绝概率,但会增加:
内存压力
排队时间
请求延迟
14.12 线程越多是不是吞吐量越高?
不是。
超过系统实际承载能力以后,更多线程可能只会增加竞争和调度成本。
十五、练习与验收
15.1 知识问答
ThreadPoolExecutor是什么?- 七参数分别是什么?
corePoolSize表示什么?maximumPoolSize表示什么?- 教材中的“临时线程”指什么?
keepAliveTime与unit为什么必须结合理解?workQueue保存的是什么?ThreadFactory负责什么?RejectedExecutionHandler负责什么?- 核心线程都忙以后,新任务第一步通常做什么?
- 什么时候才创建超过核心规模的线程?
- 什么时候拒绝任务?
- 为什么有界队列有利于资源治理?
- 无界队列有什么潜在风险?
SynchronousQueue与普通存储型队列有什么基本区别?- 四种标准拒绝策略分别是什么?
- 默认拒绝策略是什么?
CallerRunsPolicy中 Caller 是谁?- 为什么
CallerRunsPolicy具有一定反压效果? - 为什么线程池参数不存在适用于所有系统的万能数字?
15.2 代码阅读
线程池:
ExecutorService pool =
new ThreadPoolExecutor(
2,
4,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
假设所有任务都执行很久,并连续快速提交:
任务1
任务2
任务3
任务4
任务5
任务6
任务7
回答:
- 任务1、2会怎样处理?
- 任务3、4可能去哪里?
- 任务5、6为什么可能触发额外工作线程?
- 任务7为什么可能被拒绝?
- 整个线程池最多同时有几条工作线程?
- 队列最多保存几个等待任务?
再分析:
new ThreadPoolExecutor(
3,
100,
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
factory,
handler
);
回答:
- 当核心规模达到3以后,任务通常先去哪?
- 如果队列基本没有容量上限,什么时候才容易触发创建第4条线程?
- 为什么
maximumPoolSize=100可能长期发挥不了预想中的作用? - 这种配置最大的资源风险转移到了哪里?
15.3 手写代码
任务一:七参数线程池
从零手写:
new ThreadPoolExecutor(...)
要求:
核心线程:3
最大线程:5
空闲回收:10秒
队列容量:3
默认线程工厂
CallerRunsPolicy
然后提交:
10个耗时任务
每个任务输出:
线程名称
任务编号
任务二:观察拒绝
创建:
core = 1
max = 2
queue = 1
所有任务:
sleep较长时间
快速提交多个任务。
分别替换:
AbortPolicy
DiscardPolicy
CallerRunsPolicy
观察现象。
任务三:CallerRunsPolicy
要求输出:
Thread.currentThread().getName()
证明:
被 CallerRunsPolicy 接管的任务可能由提交任务的线程执行。
15.4 Debug
下面配置:
new ThreadPoolExecutor(
5,
3,
10,
TimeUnit.SECONDS,
queue,
factory,
handler
);
判断:
corePoolSize
maximumPoolSize
之间有什么逻辑问题。
有人说:
core=3、max=5,所以第4个任务来了以后线程池一定立刻创建第4条线程。
指出错误。
有人说:
我用了无界队列,所以永远不会拒绝任务,因此系统绝对最稳定。
分析这句话的问题。
有人说:
CallerRunsPolicy 就是把任务交给 main 线程。
指出不准确之处。
15.5 综合训练
设计一个“秋招岗位信息解析服务”线程池。
系统不断收到:
JD解析任务
假设:
单个任务需要一定CPU计算
并且偶尔访问文件
请你设计:
corePoolSize
maximumPoolSize
queue capacity
keepAliveTime
rejection policy
第一版参数。
不要求得到所谓“唯一正确答案”。
真正要求:
- 解释为什么设置这个核心线程数。
- 为什么设置这个最大线程数。
- 为什么队列不能无限大。
- 队列满以后为什么选择该拒绝策略。
- 如何通过压测进一步调整参数。
- 如果任务平均处理速度低于任务到达速度很久,会发生什么?
15.6 本章验收
必须能够闭卷画出:
新任务
↓
线程数 < core?
├─ 是 → 创建线程执行
└─ 否
↓
尝试入队
↓
队列成功?
├─ 是 → 等待
└─ 否
↓
线程数 < max?
├─ 是 → 创建额外线程执行
└─ 否 → 拒绝
并能够闭卷说出七参数:
corePoolSize
maximumPoolSize
keepAliveTime
unit
workQueue
threadFactory
handler
再闭卷说出四种拒绝策略:
AbortPolicy
DiscardPolicy
DiscardOldestPolicy
CallerRunsPolicy
最后能够解释:
为什么线程池不是线程越多越好
为什么队列不是越大越好
为什么ThreadPoolExecutor本质上是在管理系统容量
做到这些,就说明 JavaSE 多线程核心主线已经形成完整闭环:
线程是什么
↓
如何创建线程
↓
如何描述任务
↓
如何获得异步结果
↓
线程如何运行
↓
为什么线程不安全
↓
如何同步与加锁
↓
如何统一管理线程
↓
如何限制线程池容量