目录
Spring Boot使用@Async实现异步调用:自定义线程池
简介:
Timer
ScheduledExecutorService
基于SpingTask实现定时任务
自定义线程池
动态添加定时任务
ThreadPoolTaskScheduler
SchedulingConfigurer
基于Quartz实现定时调度
JAVA中
,目前可以通过以下几种方式进行定时任务:
Spring
提供的一个任务调度工具,支持注解和配置文件形式,支持Cron
表达式,使用简单但功能强大。基于JDK方式实现简单定时
基于
JDK
方式一共有两种:Timer
和ScheduledExecutorService
。
Timer
是jdk提供的java.util.Timer
类。
简单示例:
@GetMapping("/timer")
public String doTimer() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
log.info("Timer定时任务启动:" + new Date());
}
}, 1000,1000);//延迟1秒启动,每1秒执行一次
return "timer";
启动后,访问即可看见控制台周期性输出信息了:
2018-08-18 21:30:35.171 INFO 13352 --- [ Timer-0] c.l.l.s.c.controller.TaskController : Timer定时任务启动:Sat Aug 18 21:30:35 CST 2018
2018-08-18 21:30:36.173 INFO 13352 --- [ Timer-0] c.l.l.s.c.controller.TaskController : Timer定时任务启动:Sat Aug 18 21:30:36 CST 2018
2018-08-18 21:30:37.173 INFO 13352 --- [ Timer-0] c.l.l.s.c.controller.TaskController : Timer定时任务启动:Sat Aug 18 21:30:37 CST 2018
2018-08-18 21:30:38.173 INFO 13352 --- [ Timer-0] c.l.l.s.c.controller.TaskController : Timer定时任务启动:Sat Aug 18 21:30:38 CST 2018
2018-08-18 21:30:39.174 INFO 13352 --- [ Timer-0] c.l.l.s.c.controller.TaskController : Timer定时任务启动:Sat Aug 18 21:30:39 CST 2018
......
相关API简单说明:
1、在特定时间执行任务,只执行一次
public void schedule(TimerTask task,Date time)
2、在特定时间之后执行任务,只执行一次
public void schedule(TimerTask task,long delay)
3、指定第一次执行的时间,然后按照间隔时间,重复执行
public void schedule(TimerTask task,Date firstTime,long period)
4、在特定延迟之后第一次执行,然后按照间隔时间,重复执行
public void schedule(TimerTask task,long delay,long period)
5、第一次执行之后,特定频率执行,与3同
public void scheduleAtFixedRate(TimerTask task,Date firstTime,long period)
6、在delay毫秒之后第一次执行,后按照特定频率执行
public void scheduleAtFixedRate(TimerTask task,long delay,long period)
参数:
取消任务使用:timer.cancel()
方法即可注销任务。
此类相对用的较少了,简单了解下。
ScheduledExecutorService
可以说是Timer
的替代类,因为Timer
不支持多线程,任务是串行的,而且也不捕获异常,假设某个任务异常了,整个Timer
就无法运行了。
简单示例:
@GetMapping("/executor")
public String ScheduledExecutorService() {
//
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
log.info("ScheduledExecutorService定时任务执行:" + new Date());
}
}, 1, 1, TimeUnit.SECONDS);//首次延迟1秒,之后每1秒执行一次
log.info("ScheduledExecutorService定时任务启动:" + new Date());
return "ScheduledExecutorService!";
}
启动后,可看见控制台按设定的频率输出:
2018-08-18 22:03:24.840 INFO 6752 --- [nio-8080-exec-1] c.l.l.s.c.controller.TaskController : ScheduledExecutorService定时任务启动:Sat Aug 18 22:03:24 CST 2018
2018-08-18 22:03:25.841 INFO 6752 --- [pool-1-thread-1] c.l.l.s.c.controller.TaskController : ScheduledExecutorService定时任务执行:Sat Aug 18 22:03:25 CST 2018
2018-08-18 22:03:26.842 INFO 6752 --- [pool-1-thread-1] c.l.l.s.c.controller.TaskController : ScheduledExecutorService定时任务执行:Sat Aug 18 22:03:26 CST 2018
2018-08-18 22:03:27.841 INFO 6752 --- [pool-1-thread-2] c.l.l.s.c.controller.TaskController : ScheduledExecutorService定时任务执行:Sat Aug 18 22:03:27 CST 2018
2018-08-18 22:03:28.840 INFO 6752 --- [pool-1-thread-1] c.l.l.s.c.controller.TaskController : ScheduledExecutorService定时任务执行:Sat Aug 18 22:03:28 CST 2018
2018-08-18 22:03:29.840 INFO 6752 --- [pool-1-thread-3] c.l.l.s.c.controller.TaskController : ScheduledExecutorService定时任务执行:Sat Aug 18 22:03:29 CST 2018
可同时设置多个任务,只需再次设置scheduleAtFixedRate
即可。
常用方法说明:
public ScheduledFuture> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);
参数说明:
public ScheduledFuture> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);
参数说明:
其他的方法大家可自行谷歌下。
使用
SpringTask
在SpringBoot
是很简单的,使用@Scheduled
注解即可轻松搞定。
0.启动类,加入@EnableScheduling
让注解@Scheduled
生效。
@SpringBootApplication
@EnableScheduling
@Slf4j
public class Chapter22Application {
public static void main(String[] args) {
SpringApplication.run(Chapter22Application.class, args);
log.info("Chapter22启动!");
}
}
1.编写一个调度类,系统启动后自动扫描,自动执行。
@Component
@Slf4j
public class ScheduledTask {
/**
* 自动扫描,启动时间点之后5秒执行一次
*/
@Scheduled(fixedRate=5000)
public void getCurrentDate() {
log.info("Scheduled定时任务执行:" + new Date());
}
}
2.启动后,控制台可就看见每5秒一次输出了:
2018-08-18 22:23:09.735 INFO 13812 --- [pool-1-thread-1] c.l.l.s.c.controller.ScheduledTask : Scheduled定时任务执行:Sat Aug 18 22:23:09 CST 2018
2018-08-18 22:23:14.734 INFO 13812 --- [pool-1-thread-1] c.l.l.s.c.controller.ScheduledTask : Scheduled定时任务执行:Sat Aug 18 22:23:14 CST 2018
2018-08-18 22:23:19.735 INFO 13812 --- [pool-1-thread-1] c.l.l.s.c.controller.ScheduledTask : Scheduled定时任务执行:Sat Aug 18 22:23:19 CST 2018
2018-08-18 22:23:24.735 INFO 13812 --- [pool-1-thread-1] c.l.l.s.c.controller.ScheduledTask : Scheduled定时任务执行:Sat Aug 18 22:23:24 CST 2018
2018-08-18 22:23:29.735 INFO 13812 --- [pool-1-thread-1] c.l.l.s.c.controller.ScheduledTask : Scheduled定时任务执行:Sat Aug 18 22:23:29 CST 2018
......
使用都是简单的,现在我们来看看注解@Scheduled
的参数意思:
initialDelay
, 定义该任务延迟执行时间。从控制台输出可以看见,多任务使用的是同一个线程。可结合上章节的异步调用来实现不同任务使用不同的线程进行任务执行。
Spring提供了Async注解来实现方法的异步调用。
即当调用Async标识的方法时,调用线程不会等待被调用方法执行完成即返回继续执行以下操作,而被调用的方法则会启动一个独立线程来执行此方法。
这种异步执行的方式通常用于处理接口中不需要返回给用户的数据处理。比如当注册的时候,只需要将用户信息返回用户,而关于信息的保存操作可以使用异步执行。
Spring提供了Scheduled注解来实现定时任务的功能。
在异步方法和定时任务功能中都是开发这自己定义需要执行的方法,然后交给Spring容器管理线程,并执行相应的方法。在使用异步方法和定时任务的时候需要特别注意的是线程池的配置以及任务中异常的处理。下面对这两个功能进行简单介绍。
开启:@EnableScheduling
package com.hc.zhxf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling //启用定时任务
public class ZhxfApplication {
public static void main(String[] args) {
SpringApplication.run(ZhxfApplication.class, args);
}
}
线程池配置:
package com.hc.zhxf;
import java.util.concurrent.Executor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
@EnableAsync //开启异步
public class RoutingInspectionExcutorConfig{
private int corePoolSize = 10;
private int maxPoolSize = 200;
private int queueCapacity = 10;
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
// 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//调度器shutdown被调用时等待当前被调度的任务完成
executor.setWaitForTasksToCompleteOnShutdown(true);
//等待时长
executor.setAwaitTerminationSeconds(60);
executor.setThreadNamePrefix("sysRobotTaskExecutor-");
executor.initialize();
return executor;
}
}
使用:@Async
package com.hc.zhxf.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.hc.zhxf.service.SysRoutingInspectionService;
@Component
public class RoutingInspectionTask {
@Autowired
private SysRoutingInspectionService sysRoutingInspectionService;
/**
* 刷新每日巡检
*/
@Scheduled(cron = "0 1 0 * * ?")
@Async("taskExecutor") //使用异步
public void checkRoutingInspection() {
sysRoutingInspectionService.checkRoutingInspection();
}
}
此时Spring会在上下文中找名称为asyncTaskThreadPool的线程池来执行此任务。
异步任务可以通过定义返回类型为Future来实现返回值,定义如下:
@Async
public Future asyncTaskWithResult() {
LOGGER.info("AsyncTaskWithResult start.");
try {
Thread.sleep(1000 * 10);
} catch (Exception e) {
return new AsyncResult<>("error" + e.getMessage());
}
LOGGER.info("AsyncTaskWithResult finished.");
return new AsyncResult<>("success");
}
单元测试代码如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class AsyncApplicationTests {
@Autowired
private AsyncTaskService asyncTaskService;
@Test
public void asyncTest() throws Exception{
Future future = asyncTaskService.asyncTaskWithResult();
while (!future.isDone()) {
System.out.println("Wait asyncTaskWithResult.");
Thread.sleep(1000);
}
System.out.println("asyncTaskWithResult result is:" + future.get());
System.out.println("asyncTask finished.");
}
}
使用注解的方式,无法实现动态的修改或者添加新的定时任务的,这个使用就需要使用编程的方式进行任务的更新操作了。可直接使用
ThreadPoolTaskScheduler
或者SchedulingConfigurer
接口进行自定义定时任务创建。
ThreadPoolTaskScheduler
是SpringTask
的核心实现类,该类提供了大量的重载方法进行任务调度。这里简单示例下,具体的大家自行搜索下,用的少不太了解呀。
0.创建一个ThreadPoolTaskScheduler
类。
第一种创建:单独
@Bean("taskExecutor")
public TaskScheduler taskExecutor() {
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
executor.setPoolSize(20);
executor.setThreadNamePrefix("oKong-taskExecutor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//调度器shutdown被调用时等待当前被调度的任务完成
executor.setWaitForTasksToCompleteOnShutdown(true);
//等待时长
executor.setAwaitTerminationSeconds(60);
return executor;
}
第二种创建:与ThreadPoolTaskExecutor一起
ThreadPoolTaskExecutor
是一个专门用于执行任务的类。ThreadPoolTaskScheduler
是一个专门用于调度任务的类。
@Configuration //添加这个注解的class,只在容器启动的时候加载一次
@EnableAsync //启用异步任务:TaskExecutor
@EnableScheduling //启用定时任务:TaskScheduler
public class CommonBeanConfigure {
@Bean("taskExecutor") //指定TaskExecutor 的bean name
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(10);
threadPoolTaskExecutor.setMaxPoolSize(20);
threadPoolTaskExecutor.setQueueCapacity(1000);
threadPoolTaskExecutor.setThreadNamePrefix("async-t-");
return threadPoolTaskExecutor;
}
@Bean("taskScheduler")
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(corePoolSize);
scheduler.setThreadNamePrefix("sysRobotTaskScheduler-");
scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//调度器shutdown被调用时等待当前被调度的任务完成
scheduler.setWaitForTasksToCompleteOnShutdown(true);
//等待时长
scheduler.setAwaitTerminationSeconds(60);
return scheduler;
}
}
1.编写一个控制类,动态设置定时任务:
@Autowired
TaskScheduler taskScheduler;
@GetMapping("/poolTask")
public String threadPoolTaskScheduler() {
taskScheduler.schedule(new Runnable() {
@Override
public void run() {
log.info("ThreadPoolTaskScheduler定时任务:" + new Date());
}
}, new CronTrigger("0/3 * * * * ?"));//每3秒执行一次
return "ThreadPoolTaskScheduler!";
}
2.启动后,访问接口,即可看见控制台每3秒输出一次:
2018-08-18 23:20:39.002 INFO 9120 --- [Kong-Executor-1] c.l.l.s.c.controller.TaskController : ThreadPoolTaskScheduler定时任务:Sat Aug 18 23:20:39 CST 2018
2018-08-18 23:20:42.000 INFO 9120 --- [Kong-Executor-1] c.l.l.s.c.controller.TaskController : ThreadPoolTaskScheduler定时任务:Sat Aug 18 23:20:42 CST 2018
2018-08-18 23:20:45.002 INFO 9120 --- [Kong-Executor-2] c.l.l.s.c.controller.TaskController : ThreadPoolTaskScheduler定时任务:Sat Aug 18 23:20:45 CST 2018
2018-08-18 23:20:48.001 INFO 9120 --- [Kong-Executor-1] c.l.l.s.c.controller.TaskController : ThreadPoolTaskScheduler定时任务:Sat Aug 18 23:20:48 CST 2018
此类十个接口,直接实现其
configurerTasks
方法即可。此方法可实现取消和重置的需求。参考:
https://blog.csdn.net/jianggujin/article/details/77937316
0.编写配置类:
@Configuration
@Slf4j
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setTaskScheduler(taskExecutor());
taskRegistrar.getScheduler().schedule(new Runnable() {
@Override
public void run() {
log.info("SchedulingConfigurer定时任务:" + new Date());
}
}, new CronTrigger("0/3 * * * * ?"));//每3秒执行一次
}
@Bean("taskExecutor")
public TaskScheduler taskExecutor() {
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
executor.setPoolSize(20);
executor.setThreadNamePrefix("oKong-Executor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//调度器shutdown被调用时等待当前被调度的任务完成
executor.setWaitForTasksToCompleteOnShutdown(true);
//等待时长
executor.setAwaitTerminationSeconds(60);
return executor;
}
}
1.启动后,控制台也可以看见每3秒输出一次:
2018-08-18 23:24:39.001 INFO 868 --- [Kong-Executor-1] c.l.l.s.chapter22.config.ScheduleConfig : SchedulingConfigurer定时任务:Sat Aug 18 23:24:39 CST 2018
2018-08-18 23:24:42.001 INFO 868 --- [Kong-Executor-1] c.l.l.s.chapter22.config.ScheduleConfig : SchedulingConfigurer定时任务:Sat Aug 18 23:24:42 CST 2018
2018-08-18 23:24:45.000 INFO 868 --- [Kong-Executor-2] c.l.l.s.chapter22.config.ScheduleConfig : SchedulingConfigurer定时任务:Sat Aug 18 23:24:45 CST 2018
由于本章节是基于
SpringBoot 1.x
版本的,所以没有基于Quartz
的starter
配置,这里直接引入了Quartz
相关依赖包来集成。
0.加入pom依赖
org.quartz-scheduler
quartz
2.2.3
org.springframework
spring-context-support
org.springframework
spring-tx
1.编写配置类。
@Configuration
@Slf4j
public class QuartzConfig {
/**
* 通过工厂类,创建job实例
* @return
*/
@Bean
public MethodInvokingJobDetailFactoryBean customJobDetailFactoryBean() {
MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
//设置执行任务的bean
jobDetail.setTargetBeanName("quartzTask");
//设置具体执行的方法
jobDetail.setTargetMethod("quartzTask");
//同步执行,上一任务未执行完,下一任务等待
//true 任务并发执行
//false 下一个任务必须等待上一任务完成
jobDetail.setConcurrent(false);
return jobDetail;
}
/**
* 通过工厂类创建Trigger
* @param jobDetailFactoryBean
* @return
* @throws ParseException
*/
@Bean(name = "cronTriggerBean")
public Trigger cronTriggerBean(MethodInvokingJobDetailFactoryBean jobDetailFactoryBean) throws ParseException {
CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean.getObject());
cronTriggerFactoryBean.setCronExpression("0/3 * * * * ?");//每3秒执行一次
cronTriggerFactoryBean.setName("customCronTrigger");
cronTriggerFactoryBean.afterPropertiesSet();
return cronTriggerFactoryBean.getObject();
}
/**
* 调度工厂类,自动注入Trigger
* @return
*/
@Bean
public SchedulerFactoryBean schedulerFactoryBean(Trigger... triggers) {
SchedulerFactoryBean bean = new SchedulerFactoryBean();
//也可以直接注入 ApplicationContext,利于 getBeansOfType获取trigger
// Map triggerMap = appContext.getBeansOfType(Trigger.class);
// if(triggerMap != null) {
// List triggers = new ArrayList<>(triggerMap.size());
// //
// triggerMap.forEach((key,trigger)->{
// triggers.add(trigger);
// });
// bean.setTriggers(triggers.toArray(new Trigger[triggers.size()]));
// }
//这里注意 对应的trigger 不能为null 不然会异常的
bean.setTriggers(triggers);
return bean;
}
@Component("quartzTask")
public class QuartzTask {
public void quartzTask() {
log.info("Quartz定时任务:" + new Date());
}
}
}
2.启动后,可以看见控制台以每3秒执行一次输出:
2018-08-18 23:42:03.019 INFO 772 --- [ryBean_Worker-2] c.l.l.s.chapter22.config.QuartzConfig : Quartz定时任务:Sun Aug 18 23:42:03 CST 2018
2018-08-18 23:42:06.002 INFO 772 --- [ryBean_Worker-3] c.l.l.s.chapter22.config.QuartzConfig : Quartz定时任务:Sun Aug 18 23:42:06 CST 2018
2018-08-18 23:42:09.002 INFO 772 --- [ryBean_Worker-4] c.l.l.s.chapter22.config.QuartzConfig : Quartz定时任务:Sun Aug 18 23:42:09 CST 2018