一. 执行流程
聊到线程池就一定会聊到线程池的执行流程, 也就是当有一个任务进入线程池之后, 线程池是如何执行的?
想要真正的了解线程池的执行流程,就得先从线程池的执行方法 execute() 说起, execute() 实现源码如下:
public void execute(Runnable command) {
if (command == null) {
throw new NullPointerException();
}
int c = ctl.get();
// 当前工作的线程数小于核心线程数
if (workerCountOf(c) < corePoolSize) {
// 创建新的线程执行此任务
if (addWorker(command, true)) {
return;
}
c = ctl.get();
}
// 检查线程池是否处于运行状态,如果是则把任务添加到队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 再次检线程池是否处于运行状态,防止在第一次校验通过后线程池关闭
// 如果是非运行状态,则将刚加入队列的任务移除
if (! isRunning(recheck) && remove(command)) {
reject(command);
}
// 如果线程池的线程数为 0 时(当 corePoolSize 设置为 0 时会发生)
else if (workerCountOf(recheck) == 0) {
addWorker(null, false); // 新建线程执行任务
}
}
// 核心线程都在忙且队列都已爆满,尝试新启动一个线程执行失败
else if (!addWorker(command, false)) {
// 执行拒绝策略
reject(command);
}
}
线程池的工作流程
总结
线程池的执行流程有 3 个重要的判断点:
- 判断当前线程数和核心线程数.
- 判断当前任务队列是否已满.
- 判断当前线程数是否已达最大线程数.
如果在经过上诉三个过程后, 得到的结果都是 true , 那么就会执行线程池的拒绝策略.
二. 拒绝策略
当任务过多且线程池的任务队列已满时, 此时就会执行线程池的拒绝策略, 线程池的拒绝策略默认有以下 4 种:
- AbortPolicy:中止策略,线程池会抛出异常并中止执行此任务.
- CallerRunsPolicy:把任务交给添加此任务的(main)线程来执行.
- DiscardPolicy:忽略此任务,忽略最新的一个任务.
- DiscardOldestPolicy:忽略最早的任务,最先加入队列的任务.
在分析JDK自带的线程池拒绝策略前,先看下JDK定义的 拒绝策略接口:
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
接口定义大概就是, 当触发拒绝策略时, 线程池会根据你设置的具体的策略, 将当前提交的任务以及线程池实例本身传递给你处理, 具体作何处理, 不同场景会有不同的考虑.
1. AbortPolicy(中止策略)
功能: 当触发拒绝策略时,直接抛出拒绝执行的异常,中止策略的意思也就是打断当前执行流程
2. CallerRunsPolicy(调用者运行策略)
功能:当触发拒绝策略时,只要线程池没有关闭,就由提交任务的当前线程处理.
3. DiscardPolicy(丢弃策略)
功能:直接静悄悄的丢弃这个任务,不触发任何动作.
4. DiscardOldestPolicy(弃老策略)
功能:如果线程池未关闭,就弹出队列头部的元素,然后尝试执行.