时间:2021-05-19
通过本文你可以了解到下面这些知识点:
Future 模式
异步编程在处理耗时操作以及多任务处理的场景下非常有用,我们可以更好的让我们的系统利用好机器的 CPU 和 内存,提高它们的利用率。多线程设计模式有很多种,Future模式是多线程开发中非常常见的一种设计模式,本文也是基于这种模式来说明 SpringBoot 对于异步编程的知识。
实战之前我先简单介绍一下 Future 模式的核心思想 吧!。
Future 模式的核心思想是 异步调用 。当我们执行一个方法时,假如这个方法中有多个耗时的任务需要同时去做,而且又不着急等待这个结果时可以让客户端立即返回然后,后台慢慢去计算任务。当然你也可以选择等这些任务都执行完了,再返回给客户端。这个在 Java 中都有很好的支持,我在后面的示例程序中会详细对比这两种方式的区别。
SpringBoot 异步编程实战
如果我们需要在 SpringBoot 实现异步编程的话,通过 Spring 提供的两个注解会让这件事情变的非常简单。
1. 自定义 TaskExecutor
很多人对于 TaskExecutor 不是太了解,所以我们花一点篇幅先介绍一下这个东西。从名字就能看出它是任务的执行者,它领导执行着线程来处理任务,就像司令官一样,而我们的线程就好比一只只军队一样,这些军队可以异步对敌人进行打击👊。
Spring 提供了TaskExecutor接口作为任务执行者的抽象,它和java.util.concurrent包下的Executor接口很像。稍微不同的 TaskExecutor接口用到了 Java 8 的语法@FunctionalInterface声明这个接口是一个函数式接口。
如果没有自定义Executor, Spring 将创建一个 SimpleAsyncTaskExecutor 并使用它。
ThreadPoolTaskExecutor 常见概念:
一般情况下不会将队列大小设为:Integer.MAX_VALUE,也不会将核心线程数和最大线程数设为同样的大小,这样的话最大线程数的设置都没什么意义了,你也无法确定当前 CPU 和内存利用率具体情况如何。
如果队列已满并且当前同时运行的线程数达到最大线程数的时候,如果再有新任务过来会发生什么呢?
Spring 默认使用的是 ThreadPoolExecutor.AbortPolicy。在Spring的默认情况下,ThreadPoolExecutor 将抛出 RejectedExecutionException 来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸缩的应用程序,建议使用 ThreadPoolExecutor.CallerRunsPolicy。当最大池被填满时,此策略为我们提供可伸缩队列。
ThreadPoolTaskExecutor 饱和策略定义:
如果当前同时运行的线程数量达到最大线程数量时,ThreadPoolTaskExecutor 定义一些策略:
2. 编写一个异步的方法
下面模拟一个查找对应字符开头电影的方法,我们给这个方法加上了@Async注解来告诉 Spring 它是一个异步的方法。另外,这个方法的返回值 CompletableFuture.completedFuture(results)这代表我们需要返回结果,也就是说程序必须把任务执行完成之后再返回给用户。
请留意completableFutureTask方法中的第一行打印日志这句代码,后面分析程序中会用到,很重要!
3. 测试编写的异步方法
请求这个接口,控制台打印出下面的内容:
2019-10-01 13:50:17.007 WARN 18793 --- [lTaskExecutor-1] g.j.a.service.AsyncService : My ThreadPoolTaskExecutor-1start this task!
2019-10-01 13:50:17.007 WARN 18793 --- [lTaskExecutor-6] g.j.a.service.AsyncService : My ThreadPoolTaskExecutor-6start this task!
2019-10-01 13:50:17.007 WARN 18793 --- [lTaskExecutor-5] g.j.a.service.AsyncService : My ThreadPoolTaskExecutor-5start this task!
2019-10-01 13:50:17.007 WARN 18793 --- [lTaskExecutor-4] g.j.a.service.AsyncService : My ThreadPoolTaskExecutor-4start this task!
2019-10-01 13:50:17.007 WARN 18793 --- [lTaskExecutor-3] g.j.a.service.AsyncService : My ThreadPoolTaskExecutor-3start this task!
2019-10-01 13:50:17.007 WARN 18793 --- [lTaskExecutor-2] g.j.a.service.AsyncService : My ThreadPoolTaskExecutor-2start this task!
Elapsed time: 1010
首先我们可以看到处理所有任务花费的时间大概是 1 s。这与我们自定义的 ThreadPoolTaskExecutor 有关,我们配置的核心线程数是 6 ,然后通过通过下面的代码模拟分配了 6 个任务给系统执行。这样每个线程都会被分配到一个任务,每个任务执行花费时间是 1 s ,所以处理 6 个任务的总花费时间是 1 s。
你可以自己验证一下,试着去把核心线程数的数量改为 3 ,再次请求这个接口你会发现处理所有任务花费的时间大概是 2 s。
另外,从上面的运行结果可以看出,当所有任务执行完成之后才返回结果。这种情况对应于我们需要返回结果给客户端请求的情况下,假如我们不需要返回任务执行结果给客户端的话呢? 就比如我们上传一个大文件到系统,上传之后只要大文件格式符合要求我们就上传成功。普通情况下我们需要等待文件上传完毕再返回给用户消息,但是这样会很慢。采用异步的话,当用户上传之后就立马返回给用户消息,然后系统再默默去处理上传任务。这样也会增加一点麻烦,因为文件可能会上传失败,所以系统也需要一点机制来补偿这个问题,比如当上传遇到问题的时候,发消息通知用户。
下面会演示一下客户端不需要返回结果的情况:
将completableFutureTask方法变为 void 类型
Controller 代码修改如下:
请求这个接口,控制台打印出下面的内容:
Elapsed time: 0
2019-10-01 14:02:44.052 WARN 19051 --- [lTaskExecutor-4] g.j.a.service.AsyncService : My ThreadPoolTaskExecutor-4start this task!
2019-10-01 14:02:44.052 WARN 19051 --- [lTaskExecutor-3] g.j.a.service.AsyncService : My ThreadPoolTaskExecutor-3start this task!
2019-10-01 14:02:44.052 WARN 19051 --- [lTaskExecutor-2] g.j.a.service.AsyncService : My ThreadPoolTaskExecutor-2start this task!
2019-10-01 14:02:44.052 WARN 19051 --- [lTaskExecutor-1] g.j.a.service.AsyncService : My ThreadPoolTaskExecutor-1start this task!
2019-10-01 14:02:44.052 WARN 19051 --- [lTaskExecutor-6] g.j.a.service.AsyncService : My ThreadPoolTaskExecutor-6start this task!
2019-10-01 14:02:44.052 WARN 19051 --- [lTaskExecutor-5] g.j.a.service.AsyncService : My ThreadPoolTaskExecutor-5start this task!
可以看到系统会直接返回给用户结果,然后系统才真正开始执行任务。
待办
Reference
https://spring.io/guides/gs/async-method/
https://medium.com/trendyol-tech/spring-boot-async-executor-management-with-threadpooltaskexecutor-f493903617d
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
背景:对python不熟悉,能看懂代码,也能实现一些简单的功能,但是对python的核心思想和编程技巧不熟,所以使Pytorch写loss的时候遇到很多麻烦,尤
额,不多介绍了,能看懂的就拿走,看不懂的拿走也没用。复制代码代码如下:Dimfs,dirwin,c,Wll,str,strr,rSetfs=CreateObje
废话不说上代码ps:能不能看懂全看造化$(document).ready(function(){$('#btn').keydown(function(){$.a
注:本文所指出的错误例子其实非常简单,任何C++的初学者都能看懂。但是这个错误也非常典型,估计很多非常严肃的代码里面都存在这样的错误。这个Bug是来自于Core
有时候有一些DOS命令需要我们在执行程序的时候调用,这需要使用C#提供的相关接口。代码如下,很简单,相信大家都能看懂,我就不赘述了。usingSystem;us