线程池系列文章可参考下表,目前已更新完毕…
| 线程池系列: | 文章 |
|---|---|
| Java基础线程池 | 深入剖析Java线程池的核心概念与源码解析:从Executors、Executor、execute逐一揭秘 |
| CompletableFuture线程池 | 从用法到源码再到应用场景:全方位了解CompletableFuture及其线程池 |
| SpringBoot默认线程池(@Async和ThreadPoolTaskExecutor) | 探秘SpringBoot默认线程池:了解其运行原理与工作方式(@Async和ThreadPoolTaskExecutor) |
| SpringBoot默认线程池和内置Tomcat线程池 | 你是否傻傻分不清SpringBoot默认线程池和内置Tomcat线程池? |
文章导图

前言
在日常编码中,特别是在处理并发编程时,Java 提供了很多便捷工具帮助我们高效运行。不过你是否也曾被 Executors、Executor 和 execute 这些名字搞得一头雾水?
它们长得这么像,究竟有什么区别呢?接下来跟着我一探究竟吧!

Executors、Executor、execute对比剖析
java.util.concurrent.Executors |
是一个工具类,提供了一些静态方法,用于创建各种类型的 ExecutorService 实例。例如,newFixedThreadPool 可以创建一个固定大小的线程池,newCachedThreadPool 可以创建一个可缓存的线程池等。它简化了创建线程池的过程,提供了一些默认的配置选项。 |
|---|---|
java.util.concurrent.Executor |
是一个接口,定义了一个单一的方法 execute,用于提交任务到执行器中。这个接口是线程池的基础接口,它没有提供直接创建线程池的方法。 |
java.util.concurrent.Executor#execute |
是 Executor 接口中唯一的方法,用于将任务提交给执行器执行。这个方法通常用于提交实现了 Runnable 接口的任务。 |
简而言之,Executors 是一个工具类,用于创建各种类型的线程池实例。Executor 是一个接口,定义了提交任务到执行器的方法。execute 方法是 Executor 接口定义的唯一方法,用于提交任务给执行器执行。
如果你需要创建线程池,可以使用 Executors 类提供的方法来创建,然后使用返回的线程池实例来提交任务。具体看我下面的这个例子就理解了!:
//Executors工具类创建线程池默认是ExecutorService,ExecutorService又继承了Executor,它是线程池最底层的基础接口!
Executor executor = Executors.newFixedThreadPool(5);
//然后用executor线程池去执行execute方法,接收一个Runnable任务
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});

Executors生成的线程池?
说到了Executors,就必须谈下用它生成的线程池,在阿里Java规约中,对Executors有一个专门的规约:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lKKPHsVd-1596103123455)(EE1DC468E4A74F7AB21E05EF9D46AFB0)]](https://i-blog.csdnimg.cn/blog_migrate/4917bce1e42d84e8c20f70eca296bbfd.png)
原因其实也很简单,会有各种OOM风险,也不便于管理,详细的我都帮你们总结成思维导图了!
- Executors.newWorkStealingPool方法之外,其他方法都有OOM风险。
- 我们可以发现Executors创建的线程池底层其实还是基于ThreadPoolExecutor的方式

线程池中的 execute 方法
讲完了Executors生成的线程池,接下来当然是看看这个线程池的核心方法execute了
execute 方法的作用
execute方法是线程池的核心方法之一,可以用来提交任务并执行,当然也可以用submit,但是实际核心逻辑都是一样的。
不管是上面的Executors生成的线程池,还是自己定义的ThreadPoolExecutor线程池,实际去执行线程的时候都会调用到我们的execute 方法,
-
它用于向线程池提交一个任务,供线程池调度执行。
-
它接受一个
Runnable对象作为参数,并将其提交给内部的工作队列。
execute的工作原理
线程池采用的是一种生产者-消费者的模型,如下图:

工作原理如下:
- 任务提交:
- 当外部提交一个任务到线程池时,线程池会根据当前的运行状态和线程数量决定如何处理这个任务。
- 任务队列和线程的管理:
- 如果当前运行的线程少于核心线程数(corePoolSize),线程池会创建一个新的工作线程来执行这个任务。
- 如果当前运行的线程数达到核心线程数,任务会被放入任务队列等待执行。
- 如果任务队列已满,并且当前运行的线程数少于最大线程数(maximumPoolSize),线程池会创建新的工作线程来处理任务。
- 如果任务队列已满,并且线程池中的线程数已经达到最大线程数,线程池会根据拒绝策略处理这个任务。
- 任务执行:
- 工作线程从任务队列中获取任务并执行。
- 执行完一个任务后,工作线程会继续从任务队列中获取下一个任务,直到任务队列为空。
- 线程的回收和销毁:
- 如果一个线程在一定时间内(keepAliveTime)没有获取到新的任务,且当前线程数超过核心线程数,那么该线程将被终止。
- 当所有任务完成后,核心线程会继续等待新任务,而非核心线程会被回收。

拒绝策略
| 策略名称 | 描述 | 适用场景 |
|---|---|---|
AbortPolicy |
默认的拒绝策略。当任务无法提交到线程池时,抛出 RejectedExecutionException 异常。 |
希望在任务无法处理时立即得到通知,并进行相应处理的场景。 |
CallerRunsPolicy |
调用者运行策略。当任务无法提交到线程池时,由提交任务的线程(即调用者线程)执行该任务。 | 希望降低任务提交速度,并且不希望丢弃任务的场景。 |
DiscardPolicy |
丢弃策略。当任务无法提交到线程池时,直接丢弃该任务,不进行任何处理。 | 可以接受任务丢失,并且不希望对系统产生过多负载的场景。 |
DiscardOldestPolicy |
丢弃最旧策略。当任务无法提交到线程池时,丢弃任务队列中最旧的未处理任务,然后尝试重新提交当前任务。 | 希望抛弃旧任务,优先处理新任务的场景。 |
在实际操作里,建议选默认的AbortPolicy或CallerRunsPolicy策略。
AbortPolicy策略:
AbortPolicy是最保险安全的,简单粗暴,一满就拒绝,无论什么情况都能保证系统不会因线程池出问题。- 缺点也明显,一满就可能丢线程,没执行到你想执行的业务逻辑。
CallerRunsPolicy策略:
CallerRunsPolicy策略是,当线程池塞不下新任务时,会让调用线程来跑,既不丢任务,又能适当减缓新任务速度,减轻线程池压力,特别适合需要高执行保证的场景。- 但它的缺点正好相反,不丢线程,但流量大时可能出问题。
想象一下,某天早上,系统访问量突然猛增,线程池马上满了,剩下的都被拒绝策略打回给调用者线程处理,就像高速公路后半段的车都要掉头走别的路,你说后面的高速会不会堵得瘫痪?
系统也是一样,这个拒绝策略在这种情况下可能让系统崩溃,在前端页面上可能出现假死、卡顿、超时未响应等问题。
当然,如果默认的策略不满足你的需求也可以通过实现
RejectedExecutionHandler接口来定义自己的拒绝策略,以满足特殊的业务需求。
class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 自定义处理逻辑
}
}
源码分析工作原理
上面讲到了线程池的工作原理或者也是工作流程,那么为什么是这样的呢?
知其然知其所以然,那我们来看看源码不就知道了!
基本知识
线程的状态
注意我们这里涉及到了线程池的状态,注意区别我们常见的线程状态,线程状态如下
线程状态指的是单个线程在其生命周期中所处的状态。Java 中的线程有以下几种状态:

| 状态 | 描述 |
|---|---|
NEW |
线程已创建但尚未启动。 |
RUNNABLE |
线程正在 Java 虚拟机中执行。 |
BLOCKED |
线程被阻塞,等待监视器锁。 |
WAITING |
线程无限期等待另一个线程执行特定操作。 |
TIMED_WAITING |
线程在指定的等待时间内等待另一个线程执行特定操作。 |
TERMINATED |
线程已退出。 |
代码示例:
Thread thread = new Thread(() -> {
// 线程任务
});
System.out.println(thread.getState()); // NEW
thread.start();
System.out.println(thread.getState()); // RUNNABLE 或其他状态
线程池的状态
线程池的状态有5种,他们的状态转换如下图所示,可以看到和线程的状态完全它们不是一回事。

ThreadPoolExecutor类存放线程池的状态信息很特别,是存储在一个int类型原子变量的高3位,而低29位用来存储线程池当前运行的线程数量。通过将线程池的状态和线程数量合二为一,可以做到一次CAS原子操作更新数据。
| 状态 | 高3位值 | 说明 |
|---|---|---|
| RUNNING | 111 | 运行状态,线程池被创建后的初始状态,能接受新提交的任务,也能处理阻塞队列中的任务。 |
| SHUTDOWN | 000 | 关闭状态,不再接受新提交的任务,但任可以处理阻塞队列中的任务。 |
| STOP | 001 | 停止状态,会中断正在处理的线程,不能接受新提交的任务,也不会处理阻塞队列中的任务。 |

最低0.47元/天 解锁文章
170万+

被折叠的 条评论
为什么被折叠?



