SpringBoot实现定时任务和异步调用

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

SpringBoot实现定时任务和异步调用

  2021-04-03 我要评论

环境:

jdk1.8;spring boot2.0.2;Maven3.3

摘要说明:

定时任务:定时任务是业务场景中经常出现的一种情况如:定时发送邮件,短信、定时统计监控数据、定时对账等

异步调用:一个都买流程可能包括下单、发货通知、短信推送、消息推送等,其实除了下单这个主要程序是主程序,其他子程序可以同时进行且不影响主程序的运行,这个时候就可以使用异步调用来调用这些子程序;

步骤:

1.定时任务

a.在spring boot主类上使用注解@EnableScheduling启动定时任务:

package com.example.demo;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
 
//启动定时任务
@EnableScheduling
@SpringBootApplication
public class DemoApplication {
 
 public static void main(String[] args) {
 SpringApplication.run(DemoApplication.class, args);
 }
}

b.实现定时任务(使用@Component注解来标注组件)

 /**
 * @模块名:demo
 * @包名:com.example.demo.test1.component
 * @描述:SchedulingComponent.java
 * @版本:1.0
 * @创建人:cc
 * @创建时间:2018年9月29日上午10:19:37
 */
 
package com.example.demo.test1.component;
 
import java.util.Date;
 
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
 
/**
 * @模块名:demo
 * @包名:com.example.demo.test1.component @类名称: SchedulingComponent
 * @类描述:【类描述】用于测试定时任务 @版本:1.0
 * @创建人:cc
 * @创建时间:2018年9月29日上午10:19:37
 */
@Component
public class SchedulingComponent {
 
 /**
 * 
 * @方法名:testScheduling1
 * @方法描述【方法功能描述】测试定时任务,没三秒执行一次
 * @修改描述【修改描述】
 * @版本:1.0
 * @创建人:cc
 * @创建时间:2018年9月29日 上午10:26:20
 * @修改人:cc
 * @修改时间:2018年9月29日 上午10:26:20
 */
 @Scheduled(fixedRate = 3000)
 public void testScheduling1() {
 
 System.out.println("执行时间为"+new Date()+"执行testScheduling1");
 }
}
@Scheduled注解和之前spring使用xml配置定时任务类似:

@Scheduled(fixedRate = 5000) :上一次开始执行时间点之后5秒再执行
@Scheduled(fixedDelay = 5000) :上一次执行完毕时间点之后5秒再执行
@Scheduled(initialDelay=1000, fixedRate=5000) :第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次
@Scheduled(cron="*/5 * * * * *") :通过cron表达式定义规则

c.上述方法写好后启动服务看下控制台结果:

2.异步调用

a.首先在spring boot主类上使用注解@EnableAsync启动异步调用

package com.example.demo;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
 
//启动异步调用
@EnableAsync
@SpringBootApplication
public class DemoApplication {
 
 public static void main(String[] args) {
 SpringApplication.run(DemoApplication.class, args);
 }
}

b.sping boot异步调用很简单,只需使用@Async注解标明方法(接口方法)异步

package com.example.demo.test1.component;
 
public interface TaskComponent {
 void test1() throws Exception;
 
 void test2() throws Exception;
 
 void test3() throws Exception;
}
package com.example.demo.test1.component.impl;
 
import java.util.Random;
 
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
 
import com.example.demo.test1.component.TaskComponent;
 
@Component
public class TaskComponentImpl implements TaskComponent {
 public static Random random = new Random();
 
 @Override
 @Async
 public void test1() throws InterruptedException {
 System.out.println("开始做任务一");
 long start = System.currentTimeMillis();
 Thread.sleep(random.nextInt(10000));
 long end = System.currentTimeMillis();
 System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
 
 }
 
 @Override
 @Async
 public void test2() throws InterruptedException {
 System.out.println("开始做任务二");
 long start = System.currentTimeMillis();
 Thread.sleep(random.nextInt(10000));
 long end = System.currentTimeMillis();
 System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
 
 }
 
 @Override
 @Async
 public void test3() throws InterruptedException {
 System.out.println("开始做任务三");
 long start = System.currentTimeMillis();
 Thread.sleep(random.nextInt(10000));
 long end = System.currentTimeMillis();
 System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
 
 }
 
}

c.使用测试类进行测试:

package com.example.demo;
 
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit4.SpringRunner;
 
import com.example.demo.test1.component.TaskComponent;
 
@RunWith(SpringRunner.class)
// 引入SpringBootTest并生成随机接口
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class AsyncTest {
 
 // 注入随机接口
 @LocalServerPort
 private int port;
 
 @Autowired
 private TaskComponent taskComponent;
 
 @Test
 public void testTask() {
 try {
 taskComponent.test1();
 taskComponent.test2();
 taskComponent.test3();
 System.out.println("执行主线程");
 // 主线程休眠10秒等待上述异步方法执行
 Thread.sleep(10000);
 }
 catch (Exception e) {
 System.out.println(e);
 }
 
 }
}

执行结果如下;可以看出三个异步方法互不影响,且不影响主线程的运行

执行主线程
开始做任务一
开始做任务二
开始做任务三
完成任务一,耗时:1401毫秒
完成任务二,耗时:4284毫秒
完成任务三,耗时:5068毫秒

d.对于这些异步执行的调用往往会给我们带来思考是不是异步调用越多越好,答案当然是否;所以在这里引入线程池来进行异步调用控制:

在spring boot主类上标注线程池:

package com.example.demo;
 
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
 
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;
 
//启动定时任务
 
@EnableScheduling
@SpringBootApplication
public class DemoApplication {
 
 public static void main(String[] args) {
 SpringApplication.run(DemoApplication.class, args);
 }
 
 // 启动异步调用
 @EnableAsync
 @Configuration
 class TaskPoolConfig {
 // 核心线程数(setCorePoolSize)10:线程池创建时候初始化的线程数
 // 最大线程数(setMaxPoolSize)20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
 // 缓冲队列(setQueueCapacity)200:用来缓冲执行任务的队列
 // 允许线程的空闲时间(setKeepAliveSeconds)60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
 // 线程池名的前缀(setThreadNamePrefix):设置好了之后可以方便我们定位处理任务所在的线程池
 // 线程池对拒绝任务的处理策略(setRejectedExecutionHandler):这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute
 // 方法的调用线程中运行被拒绝的任务(setWaitForTasksToCompleteOnShutdown);如果执行程序已关闭,则会丢弃该任务
 // setWaitForTasksToCompleteOnShutdown(true)该方法就是这里的关键,用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样这些异步任务的销毁就会先于Redis线程池的销毁。
 // 同时,这里还设置了setAwaitTerminationSeconds(60),该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
 @Bean("taskExecutor")
 public Executor taskExecutor() {
 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
 executor.setCorePoolSize(10);
 executor.setMaxPoolSize(20);
 executor.setQueueCapacity(200);
 executor.setKeepAliveSeconds(60);
 executor.setThreadNamePrefix("taskExecutor-");
 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
 executor.setWaitForTasksToCompleteOnShutdown(true);
 executor.setAwaitTerminationSeconds(60);
 return executor;
 }
 }
}

在方法实现类上使用@Async的同时标注线程池:

package com.example.demo.test1.component.impl;
 
import java.util.Random;
 
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
 
import com.example.demo.test1.component.TaskComponent;
 
@Component
public class TaskComponentImpl implements TaskComponent {
 public static Random random = new Random();
 
 @Override
 @Async("taskExecutor")
 public void test1() throws InterruptedException {
 System.out.println("开始做任务一");
 long start = System.currentTimeMillis();
 Thread.sleep(random.nextInt(10000));
 long end = System.currentTimeMillis();
 System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
 
 }
 
 @Override
 @Async("taskExecutor")
 public void test2() throws InterruptedException {
 System.out.println("开始做任务二");
 long start = System.currentTimeMillis();
 Thread.sleep(random.nextInt(10000));
 long end = System.currentTimeMillis();
 System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
 
 }
 
 @Override
 @Async("taskExecutor")
 public void test3() throws InterruptedException {
 System.out.println("开始做任务三");
 long start = System.currentTimeMillis();
 Thread.sleep(random.nextInt(10000));
 long end = System.currentTimeMillis();
 System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
 
 }
 
}

再次调用测试来发现结果没什么区别:

执行主线程
开始做任务一
开始做任务二
开始做任务三
完成任务一,耗时:1117毫秒
完成任务二,耗时:3964毫秒
完成任务三,耗时:8886毫秒

接着我们修改线程池线程数为2:

executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);

再次启动测试类可以看到,同时执行的线程数为2,只有等待前一个线程结束才能执行一个新的线程;

执行主线程
开始做任务一
开始做任务二
完成任务二,耗时:620毫秒
开始做任务三
完成任务一,耗时:2930毫秒
完成任务三,耗时:4506毫秒

3.demo地址:链接地址

您可能感兴趣的文章:

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们