SpringBoot中Async异步方法+定时任务+自定义线程池介绍
1.功能说明
Spring提供了@Async注解来实现方法的异步调用。
即当调用Async标识的方法时,调用线程不会等待被调用方法执行完成即返回继续执行下面的操作,而被调用的方法则会启动一个独立线程来执行此方法。
这种异步执行的方式通常用于处理接口中不需要返回给用户的数据处理。比如当注册的时候,只需要将用户信息返回用户,而关于信息的保存操作可以使用异步执行。
Spring提供了@Scheduled注解来实现定时任务的功能。
在异步方法和定时任务功能中都是开发者自己定义需要执行的方法,然后交给Spring容器管理线程,并执行相应的方法。在使用异步方法和定时任务的时候需要特别注意的是线程池的配置以及任务中异常的处理。
Spring线程池的选择和自定义配置线程池。
在项目中我们通常不会自己手动创建线程,而是通过统一的线程池来执行task或者异步方法,使用这种方法来避免多人团队中由于自定义线程导致的资源耗尽的问题。
2.关键注解和配置接口
2.1、功能开启注解:EnableAsync和EnableScheduling
通过在Spring的配置类中添加这两个注解来开启Spring的异步方法和定时任务的功能。
@SpringBootApplication
@EnableAsync //开启异步调用
@EnableScheduling //开启定时调用
public class AsyncAndScheduledApplication {
public static void main(String[] args) {
SpringApplication.run(AsyncAndScheduledApplication.class, args);
}
}
2.2、在Spring Boot主类中定义一个线程池
package com.zrz.demo.asyncandscheduled;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@SpringBootApplication
@EnableAsync //开启异步调用
@EnableScheduling //开启定时调用
public class AsyncAndScheduledApplication {
public static void main(String[] args) {
SpringApplication.run(AsyncAndScheduledApplication.class, args);
}
/**
* 定义一个线程池
*/
@EnableAsync
@Configuration
class TaskPoolConfig {
@Bean("taskExecutor_zrz")
public Executor taskExecutor_zrz() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);//核心线程数10:线程池创建时候初始化的线程数
executor.setMaxPoolSize(20);//最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setQueueCapacity(200);//缓冲队列200:用来缓冲执行任务的队列
executor.setKeepAliveSeconds(60);//允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
executor.setThreadNamePrefix("taskExecutor_zrz-");//线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//线程池对拒绝任务的处理策略:这里采用了 CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
return executor;
}
}
}
2.3、我们如何让异步调用的定时任务使用这个自定义线程池中的资源来运行呢?方法非常简单,我们只需要在 @Async注解中指定线程池名即可,比如:@Async("taskExecutor_zrz")
package com.zrz.demo.asyncandscheduled.task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.concurrent.Future;
/**
* 异步、定时任务、自定义线程池
*/
@Component //将bean注入spring
@EnableScheduling //开启定时任务
public class CustomTask {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Async("taskExecutor_zrz") //taskExecutor_zrz:自定义线程池名称
@Scheduled(cron = "0 */1 * * * ?") //1分钟执行一次
public void syncProject() {
logger.info("******************自定义线程池执行****************************************");
}
/**
* 带返回结果的异步方法
* @return
*/
@Async //默认线程池
@Scheduled(cron = "0 */1 * * * ?") //1分钟执行一次
public Future<String> syncMessage() {
logger.info("******************Async返回操作结果****************************************");
return new AsyncResult<>("是我返回的哦");
}
}
3.验证结果
我们可以在控制台中看到输出的线程名前有之前我们定义的线程池前缀名开始的,说明我们使用线程池来执行异步任务的试验成功了!
2021-03-11 10:15:00.028 INFO 24268 --- [cTaskExecutor-2] c.zrz.demo.asyncandscheduled.task.Task : ******************默认线程池执行****************************************
2021-03-11 10:15:00.029 INFO 24268 --- [cTaskExecutor-1] c.z.d.asyncandscheduled.task.CustomTask : ******************Async返回操作结果****************************************
2021-03-11 10:15:00.029 INFO 24268 --- [kExecutor_zrz-1] c.z.d.asyncandscheduled.task.CustomTask : ******************自定义线程池执行****************************************
2021-03-11 10:16:00.003 INFO 24268 --- [kExecutor_zrz-2] c.z.d.asyncandscheduled.task.CustomTask : ******************自定义线程池执行****************************************
2021-03-11 10:16:00.003 INFO 24268 --- [cTaskExecutor-4] c.zrz.demo.asyncandscheduled.task.Task : ******************默认线程池执行****************************************
2021-03-11 10:16:00.004 INFO 24268 --- [cTaskExecutor-3] c.z.d.asyncandscheduled.task.CustomTask : ******************Async返回操作结果****************************************
2021-03-11 10:17:00.003 INFO 24268 --- [cTaskExecutor-6] c.z.d.asyncandscheduled.task.CustomTask : ******************Async返回操作结果****************************************
2021-03-11 10:17:00.005 INFO 24268 --- [kExecutor_zrz-3] c.z.d.asyncandscheduled.task.CustomTask : ******************自定义线程池执行****************************************
2021-03-11 10:17:00.005 INFO 24268 --- [cTaskExecutor-5] c.zrz.demo.asyncandscheduled.task.Task : ******************默认线程池执行****************************************
4.结束语,提下Spring在执行异步任务或者方法的时候是怎么选择线程池的
4.1、Async对于线程池的选择顺序,如下图:
Spring在执行async标识的异步方法的时候首先会在Spring的上下文中搜索类型为TaskExecutor或者名称为“taskExecutor”的bean,当可以找到的时候,就将任务提交到此线程池中执行。当不存在以上线程池的时候,Spring会手动创建一个SimpleAsyncTaskExecutor执行异步任务。
另外当标识async注解的时候指定了,value值,Spring会使用指定的线程池执行。比如:@Async("taskExecutor_zrz")
这个时候Spring会去上下文中找名字为taskExecutor_zrz的bean,并执行异步任务,找不到,会抛出异常。
4.2、Scheduled对于线程池的选择顺序
当Spring执行定时任务的时候,首先会在上下文中找类型为TaskScheduler或者名称为taskScheduler的bean,找不到的时候会手动创建一个线程执行此task。