Spring自带的线程池ThreadPoolTaskExecutor

Posted god-jiang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring自带的线程池ThreadPoolTaskExecutor相关的知识,希望对你有一定的参考价值。

前言

上一篇分享了JDK自带的线程池ThreadPoolExecutor的配置和参数详解,然而我们实际开发中更多的是使用SpringBoot来开发,Spring默认也是自带了一个线程池方便我们开发,它就是ThreadPoolTaskExecutor,接下来我们就来聊聊Spring的线程池吧。

Spring默认线程池simpleAsyncTaskExecutor

Spring异步线程池的接口类是TaskExecutor,本质还是java.util.concurrent.Executor,没有配置的情况下,默认使用的是simpleAsyncTaskExecutor。

@Async演示Spring默认的simpleAsyncTaskExecutor

@Component
@EnableAsync
public class ScheduleTask 

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Async
    @Scheduled(fixedRate = 2000)
    public void testScheduleTask() 
        try 
            Thread.sleep(6000);
            System.out.println("Spring1自带的线程池" + Thread.currentThread().getName() + "-" + sdf.format(new Date()));
         catch (InterruptedException e) 
            e.printStackTrace();
        
    

    @Async
    @Scheduled(cron = "*/2 * * * * ?")
    public void testAsyn() 
        try 
            Thread.sleep(1000);
            System.out.println("Spring2自带的线程池" + Thread.currentThread().getName() + "-" + sdf.format(new Date()));
         catch (Exception ex) 
            ex.printStackTrace();
        
    

从运行结果可以看出Spring默认的@Async用线程池名字为SimpleAsyncTaskExecutor,而且每次都会重新创建一个新的线程,所以可以看到TaskExecutor-后面带的数字会一直变大。

simpleAsyncTaskExecutor的特点是,每次执行任务时,它会重新启动一个新的线程,并允许开发者控制并发线程的最大数量(concurrencyLimit),从而起到一定的资源节流作用。默认是concurrencyLimit取值为-1,即不启用资源节流。

Spring的线程池ThreadPoolTaskExecutor

上面介绍了Spring默认的线程池simpleAsyncTaskExecutor,但是Spring更加推荐我们开发者使用ThreadPoolTaskExecutor类来创建线程池,其本质是对java.util.concurrent.ThreadPoolExecutor的包装。

这个类则是spring包下的,是Spring为我们开发者提供的线程池类,这里重点讲解这个类的用法。

Spring提供了xml给我们配置ThreadPoolTaskExecutor线程池,但是现在普遍都在用SpringBoot开发项目,所以直接上yaml或者properties配置即可,或者也可以使用@Configuration配置也行,下面演示配置和使用。

ThreadPoolTaskExecutor配置

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=god-jiang-task-

AsyncScheduledTaskConfig.java

@Configuration
public class AsyncScheduledTaskConfig 

    @Value("$spring.task.execution.pool.core-size")
    private int corePoolSize;
    @Value("$spring.task.execution.pool.max-size")
    private int maxPoolSize;
    @Value("$spring.task.execution.pool.queue-capacity")
    private int queueCapacity;
    @Value("$spring.task.execution.thread-name-prefix")
    private String namePrefix;
    @Value("$spring.task.execution.pool.keep-alive")
    private int keepAliveSeconds;
    @Bean
    public Executor myAsync() 
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //最大线程数
        executor.setMaxPoolSize(maxPoolSize);
        //核心线程数
        executor.setCorePoolSize(corePoolSize);
        //任务队列的大小
        executor.setQueueCapacity(queueCapacity);
        //线程前缀名
        executor.setThreadNamePrefix(namePrefix);
        //线程存活时间
        executor.setKeepAliveSeconds(keepAliveSeconds);

        /**
         * 拒绝处理策略
         * CallerRunsPolicy():交由调用方线程运行,比如 main 线程。
         * AbortPolicy():直接抛出异常。
         * DiscardPolicy():直接丢弃。
         * DiscardOldestPolicy():丢弃队列中最老的任务。
         */
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        //线程初始化
        executor.initialize();
        return executor;
    

在方法上添加@Async注解,然后还需要在@SpringBootApplication启动类或者@Configuration注解类上 添加注解@EnableAsync启动多线程注解,@Async就会对标注的方法开启异步多线程调用,注意,这个方法的类一定要交给Spring容器来管理

@Component
@EnableAsync
public class ScheduleTask 

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Async("myAsync")
    @Scheduled(fixedRate = 2000)
    public void testScheduleTask() 
        try 
            Thread.sleep(6000);
            System.out.println("Spring1自带的线程池" + Thread.currentThread().getName() + "-" + sdf.format(new Date()));
         catch (InterruptedException e) 
            e.printStackTrace();
        
    

    @Async("myAsync")
    @Scheduled(cron = "*/2 * * * * ?")
    public void testAsyn() 
        try 
            Thread.sleep(1000);
            System.out.println("Spring2自带的线程池" + Thread.currentThread().getName() + "-" + sdf.format(new Date()));
         catch (Exception ex) 
            ex.printStackTrace();
        
    

以上从运行结果可以看出,自定义ThreadPoolTaskExecutor可以实现线程的复用,而且还能控制好线程数,写出更好的多线程并发程序。

另外需要注意的是:关于注解失效需要注意以下几点

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

上面注解中已经注释了参数的详解,这里重点讲解一下Spring线程池的拒绝策略和处理流程。

拒绝策略

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

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

处理流程

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

总结

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

希望以上的分享对你们有所帮助,喜欢的点个赞支持一下我吧~~~

以上是关于Spring自带的线程池ThreadPoolTaskExecutor的主要内容,如果未能解决你的问题,请参考以下文章

如何使用java语言实现一个线程池,不使用jdk自带的线程池

springboot async 异步多线程

jdbc连接池问题 使用的是spring框架自带的jdbctemplate,请问一下在同一次请求中

JDK定时线程池源码解读

使用java自带线程池

使用jdk自带的线程池。加载10个线程。