解密SpringBoot线程池

开发 前端
可以发现在当前环境下 task-${id} 这个 id 并不是一直增长的,而是一直在复用 1-8。这个时候可能就会有的小伙伴们会比较好奇,默认的不是 SimpleAsyncTaskExecutor 吗?为什么从日志打印的效果上看像是一直在复用 8 个线程,难道用的是 ThreadPoolTaskExecutor?

哈喽,大家好,我是了不起。

我们在日常开发中,经常跟多线程打交道,Spring 为我们提供了一个线程池方便我们开发,它就是 ThreadPoolTaskExecutor ,接下来我们就来聊聊 Spring 的线程池吧。

使用@Async声明多线程

SpringBoot 提供了注解 @Async 来使用线程池, 具体使用方法如下:

  1. 在启动类(配置类)添加@EnableAsync来开启线程池
  2. 在需要开启子线程的方法上添加注解 @Async

下面是一个简单的例子:

@Component
@EnableAsync
@EnableScheduling
public class ScheduleTask {

    @Async
    @Scheduled(fixedRate = 2000)
    public void testAsync1() {
        try {
            Thread.sleep(6000);
            System.out.println(LocalDateTime.now() + "--线程1:" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Async
    @Scheduled(cron = "*/2 * * * * ?")
    public void testAsync2() {
        try {
            Thread.sleep(1000);
            System.out.println(LocalDateTime.now() + "--线程2:" + Thread.currentThread().getName());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

启动项目,得到如下日志结果:

图片图片

可以发现在当前环境下 task-${id} 这个 id 并不是一直增长的,而是一直在复用 1-8。这个时候可能就会有的小伙伴们会比较好奇,默认的不是 SimpleAsyncTaskExecutor 吗?为什么从日志打印的效果上看像是一直在复用 8 个线程,难道用的是 ThreadPoolTaskExecutor?

原因是 SpringBoot2.1.0 版本后,新增了 TaskExecutionAutoConfiguration 配置类。其中声明的默认线程池就是 ThreadPoolTaskExecutor 。而 @Async 在选择执行器的时候会先去 IOC 容器中先找是否有 TaskExecutor 的 Bean对象,所以在当前版本 SpringBoot 中,@Async 的默认 TaskExecutor 是 ThreadPoolTaskExecutor。

线程池配置

在 SpringBoot 项目中,我们可以在 yaml 或者 properties 配置文件中配置,或者使用 @Configuration 配置,下面演示配置方法。

  1. application.properties配置文件中配置
# 核心线程池数
spring.task.execution.pool.core-size=5
# 最大线程池数
spring.task.execution.pool.max-size=10
# 任务队列的容量
spring.task.execution.pool.queue-capacity=5
# 非核心线程的存活时间
spring.task.execution.pool.keep-alive=60
# 线程池的前缀名称
spring.task.execution.thread-name-prefix=test-task-
  1. 配置类中配置
@Bean(name = "myThreadPoolTaskExecutor")
public ThreadPoolTaskExecutor getMyThreadPoolTaskExecutor() {
    ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
    int i = Runtime.getRuntime().availableProcessors();
    taskExecutor.setCorePoolSize(i * 2);
    taskExecutor.setMaxPoolSize(i * 2);
    taskExecutor.setQueueCapacity(i * 2 * 100);
    taskExecutor.setKeepAliveSeconds(60);
    taskExecutor.setThreadNamePrefix("my-task-");
    taskExecutor.initialize();
    return taskExecutor;
}

拒绝策略

RejectedExectutionHandler 参数字段用于配置绝策略,常用拒绝策略如下

  • AbortPolicy:用于被拒绝任务的处理程序,它将抛出RejectedExecutionException
  • CallerRunsPolicy:用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。
  • DiscardOldestPolicy:用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute。
  • DiscardPolicy:用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。

处理流程

  1. 查看核心线程池是否已满,不满就创建一条线程执行任务,否则执行第2步。
  2. 查看任务队列是否已满,不满就将任务存储在任务队列中,否则执行第3步。
  3. 查看线程池是否已满,即就是是否达到最大线程池数,不满就创建一条线程执行任务,否则就按照策略处理无法执行的任务。

使用注意

  1. 注解的方法必须是 public 方法。
  2. 方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的,因为 @Transactional 和 @Async 注解的实现都是基于 Spring 的 AOP ,而 AOP 的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过 Spring 容器。
  3. 异步方法使用注解 @Async 的返回值只能为 void 或者 Future。

总结

上面简单介绍了 Spring 自带的线程池 ThreadPoolTaskExecutor 的配置和使用,并且讲了线程池的参数和处理流程。当然Spring提供了7个线程池的实现,感兴趣的可以自行了解~

责任编辑:武晓燕 来源: Java技术指北
相关推荐

2021-06-17 06:57:10

SpringBoot线程池设置

2024-02-28 09:54:07

线程池配置

2024-07-09 10:13:15

2023-05-19 08:01:24

Key消费场景

2024-07-15 08:20:24

2023-03-06 08:49:02

加密和解密SpringBoot

2020-12-10 08:24:40

线程池线程方法

2012-05-15 02:18:31

Java线程池

2023-06-07 13:49:00

多线程编程C#

2019-12-27 09:09:42

Tomcat线程池JDK

2024-01-31 08:26:44

2017-01-10 13:39:57

Python线程池进程池

2020-03-05 15:34:16

线程池C语言局域网

2020-09-04 10:29:47

Java线程池并发

2013-05-28 13:57:12

MariaDB

2011-06-22 15:50:45

QT 线程

2012-02-29 13:26:20

Java

2024-11-21 07:00:00

线程池Java开发

2021-09-11 15:26:23

Java多线程线程池

2013-06-08 13:07:23

Java线程池调度器
点赞
收藏

51CTO技术栈公众号