在项目开发中,经常需要定时任务来帮助我们来做一些内容,比如定时发送短信/站内信息、数据汇总统计、业务监控等,所以就要用到我们的定时任务,在Spring Boot中编写定时任务是非常简单的事,下面通过实例介绍如何在Spring Boot中创建定时任务
只需要引入 Spring Boot Starter jar包即可,Spring Boot Starter 包中已经内置了定时的方法
org.springframework.boot
spring-boot-starter
在Spring Boot的主类中加入**@EnableScheduling** 注解,启用定时任务的配置
package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class ScheduleTaskApplication {
public static void main(String[] args) {
SpringApplication.run(ScheduleTaskApplication.class, args);
}
}
package com.task;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
//定时任务
@Component
public class SchedulerTask {
private static final SimpleDateFormat f=new SimpleDateFormat("HH:mm:ss");
@Scheduled(fixedRate = 5000)//5秒执行一次
public void processFixedRate(){
System.out.println("processFixedRate方式开启定时任务:现在的时间是"+f.format(new Date()));
}
}
在上面的入门例子中,使用了@Scheduled(fixedRate = 5000) 注解来定义每过5秒执行的任务,对于@Scheduled 的使用可以总结 如下几种方式:
还可以用另一种方式实现定时任务,只需修改测试类即可
package com.task;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
//定时任务
@Component
public class SchedulerTask {
private static final SimpleDateFormat f=new SimpleDateFormat("HH:mm:ss");
@Scheduled(cron = "*/5 * * * * *")
public void processFixedRate(){
System.out.println("processFixedRate方式开启定时任务:现在的时间是"+f.format(new Date()));
}
}
cron 一共有七位,最后一位是年,Spring Boot 定时方案中只需要设置六位即可
cron 中,还有一些特殊的符号,含义如下:
下面列举几个常用的例子: 0 0 1 * * ? :每天凌晨1 点执行; 0 5 1 * * ?:每天 凌晨1 点 5 分执行;
同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行
创建三个处理函数分别模拟三个执行任务的操作,操作消耗时间随机取(10秒内)
package com.task;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.Random;
//同步调用
@Component
public class AsyncTask {
public static Random random = new Random();
public void testTask1() throws Exception{
System.out.println("开启任务一");
long starttime = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long endtime = System.currentTimeMillis();
System.out.println("完成任务一消耗的时间"+(endtime-starttime)+"毫秒");
}
public void testTask2() throws Exception{
System.out.println("开启任务二");
long starttime = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long endtime = System.currentTimeMillis();
System.out.println("完成任务二消耗的时间"+(endtime-starttime)+"毫秒");
}
public void testTask3() throws Exception{
System.out.println("开启任务三");
long starttime = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long endtime = System.currentTimeMillis();
System.out.println("完成任务三消耗的时间"+(endtime-starttime)+"毫秒");
}
}
package com;
import com.task.AsyncTask;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ScheduleTaskApplicationTests {
@Test
void contextLoads() {
}
@Autowired
private AsyncTask asyncTask;
@Test
public void testTask() throws Exception{
asyncTask.testTask1();
asyncTask.testTask2();
asyncTask.testTask3();
}
}
任务一、任务二、任务三顺序的执行完了,换言之testTask1、testTask2、testTask3三个函数顺序的执行完成。
上述的同步调用虽然顺利的执行完了三个任务,但可以看到执行时间比较长,若这三个任务本身之间不存在依赖关系,可以并发执行的话,同步调用在执行效率方面就比较差,可以考虑通过异步调用的方式来并发执行异步调用指程序在顺序执行时,不等待异步调用的语句返回结果就执行后面的程序。
在Spring Boot中,我们只需要通过使用@Async 注解就能简单的将原来的同步函数变为异步函数
package com.task;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.Random;
//同步调用
@Component
public class AsyncTask {
public static Random random = new Random();
@Async
public void testTask1() throws Exception{
System.out.println("开启任务一");
long starttime = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long endtime = System.currentTimeMillis();
System.out.println("完成任务一消耗的时间"+(endtime-starttime)+"毫秒");
}
@Async
public void testTask2() throws Exception{
System.out.println("开启任务二");
long starttime = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long endtime = System.currentTimeMillis();
System.out.println("完成任务二消耗的时间"+(endtime-starttime)+"毫秒");
}
@Async
public void testTask3() throws Exception{
System.out.println("开启任务三");
long starttime = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long endtime = System.currentTimeMillis();
System.out.println("完成任务三消耗的时间"+(endtime-starttime)+"毫秒");
}
}
为了让@Async注解能够生效,还需要在Spring Boot的主程序中配置@EnableAsync
package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableAsync
public class ScheduleTaskApplication {
public static void main(String[] args) {
SpringApplication.run(ScheduleTaskApplication.class, args);
}
}
此时可以反复执行单元测试,你可能会遇到各种不同的结果:
原因是目前testTask1、testTask2、testTask3三个函数的时候已经是异步执行了。主程序在异步调用之后,主程序并不会理 会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就自动结束了,导致了不完整或是没有输出任务相关内容的 情况
为了让testTask1、testTask2、testTask3 能正常结束,假设我们需要统计一下三个任务并发执行共耗时多少,这就需要等到上述三个函数都完成调动之后记录时间,并计算结果,我们如何判断上述三个异步调用是否已经执行完成呢?我们需要使用Future 来返回异步调用的结果
package com.task;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import java.util.Random;
import java.util.concurrent.Future;
//同步调用
@Component
public class AsyncTask {
public static Random random = new Random();
@Async
public Future<String> testTask1() throws Exception{
System.out.println("开启任务一");
long starttime = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long endtime = System.currentTimeMillis();
System.out.println("完成任务一消耗的时间"+(endtime-starttime)+"毫秒");
return new AsyncResult<>("任务一完成");
}
@Async
public Future<String> testTask2() throws Exception{
System.out.println("开启任务二");
long starttime = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long endtime = System.currentTimeMillis();
System.out.println("完成任务二消耗的时间"+(endtime-starttime)+"毫秒");
return new AsyncResult<>("任务二完成");
}
@Async
public Future<String> testTask3() throws Exception{
System.out.println("开启任务三");
long starttime = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long endtime = System.currentTimeMillis();
System.out.println("完成任务三消耗的时间"+(endtime-starttime)+"毫秒");
return new AsyncResult<>("任务三完成");
}
}
package com;
import com.task.AsyncTask;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.Future;
@SpringBootTest
class ScheduleTaskApplicationTests {
@Test
void contextLoads() {
}
@Autowired
private AsyncTask asyncTask;
@Test
public void testTask() throws Exception{
// asyncTask.testTask1();
// asyncTask.testTask2();
// asyncTask.testTask3();
Future<String> taskOne = asyncTask.testTask1();
Future<String> taskTwo = asyncTask.testTask2();
Future<String> taskThree = asyncTask.testTask3();
while (true){
if (taskOne.isDone()&&taskTwo.isDone()&&taskThree.isDone()){
break;
}
Thread.sleep(10000);
}
}
}
开启异步注解 @EnableAsync 方法上加 @Async 默认实现 SimpleAsyncTaskExecutor 不是真的线程池,这个类不重用线程,每次调用 都会创建一个新的线程
package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@SpringBootApplication
@EnableAsync
public class SpringbootAsyncApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootAsyncApplication.class, args);
}
@Bean("myTaskExecutor")
public Executor myTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);//核心线程数量,线程池创建时候初始化的线程数
executor.setMaxPoolSize(15);//最大线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setQueueCapacity(200);//缓冲队列,用来缓冲执行任务的队列
executor.setKeepAliveSeconds(60);//当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
executor.setThreadNamePrefix("myTask-");//设置好了之后可以方便我们定位处理任务所在的线程池
executor.setWaitForTasksToCompleteOnShutdown(true);//用来设置线程池关闭的时候等待所有任务都完成再
继续销毁其他的Bean
executor.setAwaitTerminationSeconds(60);//该方法用来设置线程池中任务的等待时间,如果超过这个时候还没
有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
//线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在
execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
在 @Async后面加上自定义线程池名字即可
package com.task;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import java.util.Random;
import java.util.concurrent.Future;
//同步调用
@Component
public class AsyncTask {
public static Random random = new Random();
@Async("myTaskExecutor")
public Future<String> testTask1() throws Exception{
System.out.println("开启任务一");
long starttime = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long endtime = System.currentTimeMillis();
System.out.println("完成任务一消耗的时间"+(endtime-starttime)+"毫秒");
return new AsyncResult<>("任务一完成");
}
@Async("myTaskExecutor")
public Future<String> testTask2() throws Exception{
System.out.println("开启任务二");
long starttime = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long endtime = System.currentTimeMillis();
System.out.println("完成任务二消耗的时间"+(endtime-starttime)+"毫秒");
return new AsyncResult<>("任务二完成");
}
@Async("myTaskExecutor")
public Future<String> testTask3() throws Exception{
System.out.println("开启任务三");
long starttime = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long endtime = System.currentTimeMillis();
System.out.println("完成任务三消耗的时间"+(endtime-starttime)+"毫秒");
return new AsyncResult<>("任务三完成");
}
}