百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

Springboot学习记录14 异步调用:@Scheduled和@Async

yuyutoo 2024-10-12 00:06 5 浏览 0 评论

因为完全没有接触过Springboot的异步调用,所以本学习记录的代码以及说明,大部分参考自gitee码云的如下工程。鸣谢!

https://gitee.com/didispace/SpringBoot-Learning.git

本学习记录的示例代码克隆地址,分支为develop

https://gitee.com/kutilion/MyArtifactForEffectiveJava.git

本章示例代码结构

  • studySpringboot.n14.async.config.ThreadPoolConfig.java 线程池的定义类
  • studySpringboot.n14.async.task.CustomizedTask.java 使用线程池的自定义任务类
  • studySpringboot.n14.async.task.ScheduledTask.java 定时执行的任务类
  • studySpringboot.n14.async.task.AsyncTest.java 测试类,测试自定义任务类

定时任务@Scheduled

想让Springboot支持定时任务,只需要如下两个步骤:

1 在Springboot的启动类中加上@EnableScheduling注解,如:

BootApplication.java

@SpringBootApplication
// Add Swagger2 tool. Use http://localhost:8080/swagger-ui.html to access and debug restful api.
@EnableSwagger2
@EnableCaching
@EnableScheduling
public class BootApplication {
 public static void main(String[] args) {
 // ApplicationContext context = SpringApplication.run(BootApplication.class, args);
 SpringApplication sa = new SpringApplication(BootApplication.class);
//...

2 在想定时执行的的方法上使用@Scheduled注解。下面方法的注解的属性fixedRate表示每5000毫秒执行一次。主要功能就是每5秒打印一次系统时间。注释部分是它调用的自定义任务类CustomizedTask的doTaskX方法。感兴趣可以打开注释试一下。

ScheduledTask.java

 @Scheduled(fixedRate = 5000)
 public void reportCurrentTime() throws Exception {
 LocalDateTime ldt = LocalDateTime.now();
 String formatedLDT = dtf.format(ldt);
 System.out.println(formatedLDT);
// customizedTask.doTask1(formatedLDT);
// customizedTask.doTask2(formatedLDT);
// customizedTask.doTask3(formatedLDT);
 }

启动Springboot服务,可以看到如下结果。发现一个比较有意思的事情, 就是定时执行的方法居然在Springboot application stated event之前执行。

@Scheduled的其他属性

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

关于cron表达式,可以参照如下连接:

https://www.cnblogs.com/javahr/p/8318728.html

使用线程池的自定义非同步任务

定义线程池

ThreadPoolConfig.java

@Configuration
@EnableAsync
public class ThreadPoolConfig {
 @Bean("taskExecutor")
 public Executor taskExecutor() {
 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
 // 核心线程数
 executor.setCorePoolSize(10);
 // 最大线程数
 executor.setMaxPoolSize(20);
 // 缓冲执行任务的队列长度
 executor.setQueueCapacity(200);
 // 允许线程的空闲时间,超过了核心线程出之外的线程在空闲时间到达之后会被销毁
 executor.setKeepAliveSeconds(60);
 // 线程池名的前缀,输出log的时候比较有用
 executor.setThreadNamePrefix("taskExecutor-");
 // 线程池对拒绝任务的处理策略,这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。
 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
 // 用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样异步任务的销毁就会先于Redis线程池的销毁。
 executor.setWaitForTasksToCompleteOnShutdown(true);
 // 该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁
 executor.setAwaitTerminationSeconds(60);
 return executor;
 }

线程池的Bean名字为taskExecutor。这个可以自定义,需要从线程池中取得线程的方法会用到这个名字。

使用@Async和线程池实现非同步任务

CustomizedTask.java

 @Autowired
 private StringRedisTemplate stringRedisTemplate;
 
 @Async("taskExecutor")
 public void doTask1(String timing) throws Exception {
 logger.info("Mission 1 started index : " + timing);
 long start = System.currentTimeMillis();
 Thread.sleep(random.nextInt(10000));
 logger.info("Mission 1 radom rediskey: " + stringRedisTemplate.randomKey());
 long end = System.currentTimeMillis();
 logger.info("Mission 1 completed. cost " + (end - start) + "ms index : " + timing);
 }

这个类注入了redis的StringRedisTemplate,在任务中会使用到。主要是为了测试程序关闭了,但是任务没有执行完毕,并且使用程序提供的外部资源的情况。redis的方法stringRedisTemplate.randomKey会从redis缓存中随机抽取key值,所以要保证redis中有一些缓存好的数据,否则抽出的是null。可以通过redis-cli实现set一些键值对来用。

@Async注解是本章的核心,它表明标注的方法是可以非同步执行的方法。它的参数taskExecutor是线程池的名字。表明这个方法启动时需要从线程池中取得未被使用的线程。

测试

AsyncTest.java类的test1方法

 @Test
 public void test1() throws Exception {
 for (int i = 0; i < 100; i++) {
 if (i == 99) {
 System.exit(0);
 }
 customizedTask.doTask1(Integer.toString(i));
 customizedTask.doTask2(Integer.toString(i));
 customizedTask.doTask3(Integer.toString(i));
 }
 }

测试结果如图:

程序会从i=0一直执行到i=99时推出。不会出现任何异常。

但是将线程池的如下语句注释

executor.setWaitForTasksToCompleteOnShutdown(true);

当i=99的时候,程序会立即关闭,也就是说redis的连接会马上失效。这个时点没有执行完毕的任务就会抛出异常。

截图过短,异常没有显示出来,全文是

Unexpected exception occurred invoking async method: public void studySpringboot.n14.async.task.CustomizedTask.doTask2(java.lang.String) throws java.lang.Exception java.lang.InterruptedException: sleep interrupted

另外一个需要注意的是for循环的执行顺序是随机的,并不是从1到100。这是因为各个线程争夺资源导致的。for循环本身执行很快,但是它所唤起的任务需要争夺资源,所以会造成任务执行先后顺序的不确定性。

Future类的使用

Future是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果的接口。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

该接口中定义了如下5个方法

boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
 throws InterruptedException, ExecutionException, TimeoutException;

以下说明摘自,浅显易懂

http://blog.didispace.com/springbootasync-4/

  • cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
  • isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
  • isDone方法表示任务是否已经完成,若任务完成,则返回true;
  • get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
  • get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

CustomizedTask.java

该类中定义了返回Future的异步任务

 @Async("taskExecutor")
 public Future<String> doTaskWithFuture() throws Exception {
 long sleep = random.nextInt(10000);
 logger.info("Mission 4 started. Needs " + sleep + " ms.");
 Thread.sleep(sleep);
 logger.info("Mission 4 completed.");
 return new AsyncResult<>("This is the return text of doTaskWithFuture.");
 }

测试方法:

AsyncTest.java

 @Test
 public void test2() throws Exception {
 Future<String> futureResult = customizedTask.doTaskWithFuture();
 String result = futureResult.get(5, TimeUnit.SECONDS);
 System.out.println(result);
 }

该测试方法使用了get(long timeout, TimeUnit unit)方法,为异步任务设定了超时时间,5秒。如果任务执行超过了5秒则会抛出异常,如果不足5秒则会正常返回。

异常时的测试结果:

总结

  1. @Scheduled注解实现定时任务
  2. @Async注解实现异步任务
  3. 线程池的简单使用和配置
  4. Future的简单学习

相关推荐

史上最全的浏览器兼容性问题和解决方案

微信ID:WEB_wysj(点击关注)◎◎◎◎◎◎◎◎◎一┳═┻︻▄(页底留言开放,欢迎来吐槽)●●●...

平面设计基础知识_平面设计基础知识实验收获与总结
平面设计基础知识_平面设计基础知识实验收获与总结

CSS构造颜色,背景与图像1.使用span更好的控制文本中局部区域的文本:文本;2.使用display属性提供区块转变:display:inline(是内联的...

2025-02-21 16:01 yuyutoo

写作排版简单三步就行-工具篇_作文排版模板

和我们工作中日常word排版内部交流不同,这篇教程介绍的写作排版主要是用于“微信公众号、头条号”网络展示。写作展现的是我的思考,排版是让写作在网格上更好地展现。在写作上花费时间是有累积复利优势的,在排...

写一个2048的游戏_2048小游戏功能实现

1.创建HTML文件1.打开一个文本编辑器,例如Notepad++、SublimeText、VisualStudioCode等。2.将以下HTML代码复制并粘贴到文本编辑器中:html...

今天你穿“短袖”了吗?青岛最高23℃!接下来几天气温更刺激……

  最近的天气暖和得让很多小伙伴们喊“热”!!!  昨天的气温到底升得有多高呢?你家有没有榜上有名?...

CSS不规则卡片,纯CSS制作优惠券样式,CSS实现锯齿样式

之前也有写过CSS优惠券样式《CSS3径向渐变实现优惠券波浪造型》,这次再来温习一遍,并且将更为详细的讲解,从布局到具体样式说明,最后定义CSS变量,自定义主题颜色。布局...

柠檬科技肖勃飞:大数据风控助力信用社会建设

...

你的自我界限够强大吗?_你的自我界限够强大吗英文

我的结果:A、该设立新的界限...

行内元素与块级元素,以及区别_行内元素和块级元素有什么区别?

行内元素与块级元素首先,CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,分别为块级(block)、行内(inline)。块级元素:(以下列举比较常...

让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华
让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华

去年的两会期间,习近平总书记在参加人大会议四川代表团审议时,对治蜀兴川提出了明确要求,指明了前行方向,并带来了“祝四川人民的生活越来越安逸”的美好祝福。又是一年...

2025-02-21 16:00 yuyutoo

今年国家综合性消防救援队伍计划招录消防员15000名

记者24日从应急管理部获悉,国家综合性消防救援队伍2023年消防员招录工作已正式启动。今年共计划招录消防员15000名,其中高校应届毕业生5000名、退役士兵5000名、社会青年5000名。本次招录的...

一起盘点最新 Chrome v133 的5大主流特性 ?

1.CSS的高级attr()方法CSSattr()函数是CSSLevel5中用于检索DOM元素的属性值并将其用于CSS属性值,类似于var()函数替换自定义属性值的方式。...

竞走团体世锦赛5月太仓举行 世界冠军杨家玉担任形象大使

style="text-align:center;"data-mce-style="text-align:...

学物理能做什么?_学物理能做什么 卢昌海

作者:曹则贤中国科学院物理研究所原标题:《物理学:ASourceofPowerforMan》在2006年中央电视台《对话》栏目的某期节目中,主持人问过我一个的问题:“学物理的人,如果日后不...

你不知道的关于这只眯眼兔的6个小秘密
你不知道的关于这只眯眼兔的6个小秘密

在你们忙着给熊本君做表情包的时候,要知道,最先在网络上引起轰动的可是这只脸上只有两条缝的兔子——兔斯基。今年,它更是迎来了自己的10岁生日。①关于德艺双馨“老艺...

2025-02-21 16:00 yuyutoo

取消回复欢迎 发表评论: