深入剖析Java线程池的核心概念与源码解析:从Executors、Executor、execute逐一揭秘


线程池系列文章可参考下表,目前已更新完毕…

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

文章导图

image-20240601182910930

前言

在日常编码中,特别是在处理并发编程时,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());
    }
});

image-20240530222442006

Executors生成的线程池?

说到了Executors,就必须谈下用它生成的线程池,在阿里Java规约中,对Executors有一个专门的规约:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lKKPHsVd-1596103123455)(EE1DC468E4A74F7AB21E05EF9D46AFB0)]

原因其实也很简单,会有各种OOM风险,也不便于管理,详细的我都帮你们总结成思维导图了!

  • Executors.newWorkStealingPool方法之外,其他方法都有OOM风险。
  • 我们可以发现Executors创建的线程池底层其实还是基于ThreadPoolExecutor的方式

image-20240531214546898

线程池中的 execute 方法

讲完了Executors生成的线程池,接下来当然是看看这个线程池的核心方法execute了

execute 方法的作用

execute 方法是线程池的核心方法之一,可以用来提交任务并执行,当然也可以用submit,但是实际核心逻辑都是一样的。

不管是上面的Executors生成的线程池,还是自己定义的ThreadPoolExecutor线程池,实际去执行线程的时候都会调用到我们的execute 方法,

  • 它用于向线程池提交一个任务,供线程池调度执行。

  • 它接受一个 Runnable 对象作为参数,并将其提交给内部的工作队列。

execute的工作原理

线程池采用的是一种生产者-消费者的模型,如下图:
FixedThreadPool的execute()方法运行示意图

工作原理如下:

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

image-20240601013614020

拒绝策略

策略名称 描述 适用场景
AbortPolicy 默认的拒绝策略。当任务无法提交到线程池时,抛出 RejectedExecutionException 异常。 希望在任务无法处理时立即得到通知,并进行相应处理的场景。
CallerRunsPolicy 调用者运行策略。当任务无法提交到线程池时,由提交任务的线程(即调用者线程)执行该任务。 希望降低任务提交速度,并且不希望丢弃任务的场景。
DiscardPolicy 丢弃策略。当任务无法提交到线程池时,直接丢弃该任务,不进行任何处理。 可以接受任务丢失,并且不希望对系统产生过多负载的场景。
DiscardOldestPolicy 丢弃最旧策略。当任务无法提交到线程池时,丢弃任务队列中最旧的未处理任务,然后尝试重新提交当前任务。 希望抛弃旧任务,优先处理新任务的场景。

在实际操作里,建议选默认的AbortPolicyCallerRunsPolicy策略。

AbortPolicy策略:

  • AbortPolicy是最保险安全的,简单粗暴,一满就拒绝,无论什么情况都能保证系统不会因线程池出问题。
  • 缺点也明显,一满就可能丢线程,没执行到你想执行的业务逻辑。

CallerRunsPolicy策略:

  • CallerRunsPolicy策略是,当线程池塞不下新任务时,会让调用线程来跑,既不丢任务,又能适当减缓新任务速度,减轻线程池压力,特别适合需要高执行保证的场景。
  • 但它的缺点正好相反,不丢线程,但流量大时可能出问题。

想象一下,某天早上,系统访问量突然猛增,线程池马上满了,剩下的都被拒绝策略打回给调用者线程处理,就像高速公路后半段的车都要掉头走别的路,你说后面的高速会不会堵得瘫痪?

系统也是一样,这个拒绝策略在这种情况下可能让系统崩溃,在前端页面上可能出现假死、卡顿、超时未响应等问题。

当然,如果默认的策略不满足你的需求也可以通过实现 RejectedExecutionHandler 接口来定义自己的拒绝策略,以满足特殊的业务需求。

class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 自定义处理逻辑
    }
}

源码分析工作原理

上面讲到了线程池的工作原理或者也是工作流程,那么为什么是这样的呢?

知其然知其所以然,那我们来看看源码不就知道了!

基本知识

线程的状态

注意我们这里涉及到了线程池的状态,注意区别我们常见的线程状态,线程状态如下

线程状态指的是单个线程在其生命周期中所处的状态。Java 中的线程有以下几种状态:

img

状态 描述
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种,他们的状态转换如下图所示,可以看到和线程的状态完全它们不是一回事。

图3 线程池生命周期

ThreadPoolExecutor类存放线程池的状态信息很特别,是存储在一个int类型原子变量的高3位,而低29位用来存储线程池当前运行的线程数量。通过将线程池的状态和线程数量合二为一,可以做到一次CAS原子操作更新数据。

<
状态 高3位值 说明
RUNNING 111 运行状态,线程池被创建后的初始状态,能接受新提交的任务,也能处理阻塞队列中的任务。
SHUTDOWN 000 关闭状态,不再接受新提交的任务,但任可以处理阻塞队列中的任务。
STOP 001 停止状态,会中断正在处理的线程,不能接受新提交的任务,也不会处理阻塞队列中的任务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Apple_Web

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值