听说可以十分钟掌握Spring Boot 集成定时任务、异步调用?

简介: 在项目开发中,经常需要定时任务来帮助我们来做一些内容,比如定时发送短信/站内信息、数据汇总统计、业务监控等,所以就要用到我们的定时任务,在Spring Boot中编写定时任务是非常简单的事,下面通过实例介绍如何在Spring Boot中创建定时任务

1. 定时任务


在项目开发中,经常需要定时任务来帮助我们来做一些内容,比如定时发送短信/站内信息、数据汇总统计、业务监控等,所以就要用到我们的定时任务,在Spring Boot中编写定时任务是非常简单的事,下面通过实例介绍如何在Spring Boot中创建定时任务


1.1 @Scheduled-fixedRate方式


1.1.1  pom配置


只需要引入 Spring Boot Starter jar包即可,Spring Boot Starter 包中已经内置了定时的方法


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
复制代码


1.1.2 加入注解


在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);
    }
}
复制代码


1.1.3 创建测试类


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()));
    }
}
复制代码


1.1.4 参数说明


在上面的入门例子中,使用了@Scheduled(fixedRate = 5000) 注解来定义每过5秒执行的任务,对于@Scheduled 的使用可以总结 如下几种方式:


  1. @Scheduled(fixedRate = 5000) :上一次开始执行时间点之后5秒再执行
  2. @Scheduled(fixedDelay = 5000) :上一次执行完毕时间点之后5秒再执行
  3. @Scheduled(initialDelay=1000, fixedRate=5000) :第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次


1.1.5 运行测试


1.JPG


1.2 @Scheduled-cron方式


还可以用另一种方式实现定时任务,只需修改测试类即可


1.2.1 修改测试类


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()));
    }
}
复制代码


1.2.2 测试


2.JPG


1.2.3 参数说明


cron 一共有七位,最后一位是年,Spring Boot 定时方案中只需要设置六位即可


  1. 第一位,表示秒,取值 0 ~ 59;
  2. 第二位,表示分,取值 0 ~ 59;
  3. 第三位,表示小时,取值 0 ~ 23;
  4. 第四位,日期天/日,取值 1 ~31;
  5. 第五位,日期月份,取值 1~12;
  6. 第六位,星期,取值 1 ~ 7,星期一,星期二...,注,1 表示星期 天,2 表示星期一;
  7. 第七位,年份,可以留空,取值 1970 ~ 2099


cron 中,还有一些特殊的符号,含义如下:


  1. (*)星号,可以理解为每的意思,每秒、每分、每天、每月、每年..(?)问号,问号只能出现在日期和星期这两个位置,表示这个位置的值不确定(-)减号,表达一个范围,如在小时字段中使用“10 ~ 12”,则表示从 10 到 12 点,即 10、11、12
  2. (,)逗号,表达一个列表值,如在星期字段中使用“1、2、4”,则表示星期一、星期二、星期四
  3. (/)斜杠,如 x/y,x 是开始值,y 是步⻓长,比如在第一位(秒),0/15 就是从 0 秒开始,每隔 15 秒执 行一次。


下面列举几个常用的例子: 0 0 1 * * ? :每天凌晨1 点执行; 0 5 1 * * ?:每天 凌晨1 点 5 分执行;


2. 异步调用


2.1 同步调用


同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行


2.1.1 定义一个Task类


创建三个处理函数分别模拟三个执行任务的操作,操作消耗时间随机取(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)+"毫秒");
    }
}
复制代码


2.1.2 创建测试类


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();
    }
}
复制代码


2.1.3 测试


3.JPG


任务一、任务二、任务三顺序的执行完了,换言之testTask1testTask2testTask3三个函数顺序的执行完成。


2.2 异步调用


上述的同步调用虽然顺利的执行完了三个任务,但可以看到执行时间比较长,若这三个任务本身之间不存在依赖关系,可以并发执行的话,同步调用在执行效率方面就比较差,可以考虑通过异步调用的方式来并发执行异步调用指程序在顺序执行时,不等待异步调用的语句返回结果就执行后面的程序。


在Spring Boot中,我们只需要通过使用@Async 注解就能简单的将原来的同步函数变为异步函数


2.2.1 修改Task类


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)+"毫秒");
    }
}
复制代码


2.2.2 修改SpringbootAsyncApplication


为了让@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);
    }
}
复制代码


此时可以反复执行单元测试,你可能会遇到各种不同的结果:


  • 没有任何任务相关的输出
  • 有部分任务相关的输出
  • 乱序的任务相关的输出


原因是目前testTask1testTask2testTask3三个函数的时候已经是异步执行了。主程序在异步调用之后,主程序并不会理 会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就自动结束了,导致了不完整或是没有输出任务相关内容的 情况


2.3 异步调用结果返回


为了让testTask1testTask2testTask3 能正常结束,假设我们需要统计一下三个任务并发执行共耗时多少,这就需要等到上述三个函数都完成调动之后记录时间,并计算结果,我们如何判断上述三个异步调用是否已经执行完成呢?我们需要使用Future 来返回异步调用的结果


2.3.1  改造AsyncTask


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<>("任务三完成");
    }
}
复制代码


2.3.2 改造测试类


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);
        }
    }
}
复制代码


2.3.3 测试


4.JPG


2.3.4 总结


  • 在测试用例一开始记录开始时间
  • 在调用三个异步函数的时候,返回Future 类型的结果对象
  • 在调用完三个异步函数之后,开启一个循环,根据返回的Future 对象来判断三个异步函数是否都结束了。若都结束,就结束循环;若没有都结束,就等1秒后再判断。
  • 跳出循环之后,根据结束时间 - 开始时间,计算出三个任务并发执行的总耗时


2.4 异步调用自定义线程池


开启异步注解 @EnableAsync 方法上加 @Async 默认实现 SimpleAsyncTaskExecutor 不是真的线程池,这个类不重用线程,每次调用 都会创建一个新的线程


2.4.1 自定义线程池


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;
    }
}
复制代码


2.4.2 改造AsyncTask


@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<>("任务三完成");
    }
}



相关文章
|
2月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
240 0
|
2月前
|
Java Maven Docker
gitlab-ci 集成 k3s 部署spring boot 应用
gitlab-ci 集成 k3s 部署spring boot 应用
|
26天前
|
XML Java API
Spring Boot集成MinIO
本文介绍了如何在Spring Boot项目中集成MinIO,一个高性能的分布式对象存储服务。主要步骤包括:引入MinIO依赖、配置MinIO属性、创建MinIO配置类和服务类、使用服务类实现文件上传和下载功能,以及运行应用进行测试。通过这些步骤,可以轻松地在项目中使用MinIO的对象存储功能。
|
27天前
|
消息中间件 Java Kafka
什么是Apache Kafka?如何将其与Spring Boot集成?
什么是Apache Kafka?如何将其与Spring Boot集成?
65 5
|
1月前
|
消息中间件 Java Kafka
Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
44 1
|
1月前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
45 2
|
2月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
81 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
1月前
|
消息中间件 监控 Java
您是否已集成 Spring Boot 与 ActiveMQ?
您是否已集成 Spring Boot 与 ActiveMQ?
53 0
|
2月前
|
Java BI 调度
Java Spring的定时任务的配置和使用
遵循上述步骤,你就可以在Spring应用中轻松地配置和使用定时任务,满足各种定时处理需求。
166 1
|
2月前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
247 1