目录
- 一、什么是异步?
- 二、为什么要使用异步编程?
- 三、SpringBoot开启异步编程
- 1、有返回值的异步方法
- 2、@Async使用的线程池
- 3、SpringBoot使用的默认线程池源码解析
- 4、线程池配置
- 四、结语
一、什么是异步?
现在我们假设有一个接口方法,里面又调用了三个子方法,分别是A,B,C。先从A执行,执行完毕再执行B,B执行完最后执行C。这也是我们代码最常见的执行方式。ABC顺序执行,其中一个出问题了,如果抛出了异常,后续则不再执行。这中方式就是同步执行。
那么异步执行是什么样子的呢?
假设B方法改为异步,那么A方法执行完毕,执行B方法。此时不需要等B方法执行完毕,代码会直接执行C方法。也就是B方法不再影响C方法的执行,这里B方法就是异步执行。
二、为什么要使用异步编程?
异步方法的作用也很明显,假设我们上面的接口方法是一个用户www.devze.com注册方法,A方法注册成功,B方法是增加积分,C方法增加权限。那么A方法执行成功后,我们就可以给用户添加权限了。至于增加积分,完全可以异步处理,这样注册的效率就会更高,提高用户体验。实际上的业务场景还有很多,通常需要异步的都是执行比较慢,又对我们串行执行的业务逻辑没有影响,为了提高代码效率,我们就需要异步执行。
三、SpringBoot开启异步编程
接下来,我们就以SpringBoot项目为例,来看下如何使用异步编程。
第一步,通常来说一个新功能都是从引入包开始,因为这个功能本身是Spring 3提供的功能,所以我们可以直接使用。首先在主启动类上android通过注解@EnableAsync开启异步功能。
@SpringBootApplication @EnableAsync public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
第二步,在需要异步的方法上,添加注解@Async,这样就可以了。
@Async public void testAsync() { xxxx; }
是不是非常简单?使用当然非常简单了,但是这里面有许多坑和业务场景,我们就来一一说明。
1、有返回值的异步方法
@RestController public class TestController { public Result testAsync(){ service.testA(); service.testB(); service.testC(); return Result.success(); } } @Service public class service{ void testA(){ xxxx; }; @Async void testB(){ xxxx; }; void testC(){ xxxx; }; }
我们在Controller层的testAsync接口中,分别调用了service层中的testA,testB,testC方法。其中testB我们加了@Asyn编程客栈c异步注解,那么在主线程testAsync中执行testB就无需等待,会继续执行testC方法。
如果是没有返回值的testB方法,那么这样就可以满足我们的需求了,但是如果testB有返回值呢,而且需要我们对返回值进行存储。那么就需要有返回值的异步方法。这个也比较简单,我们只需要使用Future(Java.util类中的)来进行返回。
@RestController public class TestController { public Result testAsync(){ service.testA(); Future future = service.testB(); //testC不许等待testB执行完毕,即可执行 service.testC(); //get()会一直等待有结果返回 log.info(future.get()); return Result.success(); } } @Service public class service{ void testA(){ xxxx; }; @Async Future<String> testB(){ xxxx; return new AsyncResult<>("返回值结果); }; void testC(){ xxxx; }; }
看testB方法,我们改为Future类型的返回。此时就可以获得异步方法的返回了,但是注意我们这里是异步,所以在使用返回结果future时,可能是没有值的,因为我们异步方法可能没有执行完毕。这里需要我们使用future.get()方法获取返回结果,get方法会自旋一直等待,直到有返回结果才继续往下执行。
这里使用get获取结果时要注意了,如果放在testC前面获取异步结果,就会导致testC无法串行执行,所以一定要根据业务场景来使用http://www.devze.com。比如我们将获取testB的结果放在了最后一行,testC串行执行,在最终返回结果前再获取异步testB方法的返回结果。
2、@Async使用的线程池
在使用@Async注解时,你一定从网上看到过许多人说,不要使用默认线程池,这个是怎么回事呢?
首先,我们要明白,@Async的异步实现,其实就是起一个新的线程,和主线程区分开,来执行异步的方法。这样你主线程继续串行执行,我新起的子线程执行你的异步方法,互不影响,这样就实现了异步执行的目的。
牵扯到多线程,那就一定离不开线程池的使用,这个在这里不详细说,我会在线程池相关的文章里说明。回到刚才的问题,网上许多人说的,不要使用默认线程池,是因为@Async以前版本使用的默认线程池是SimpleAsyncTaskExecutor,这个线程池有许多问题,推荐使用的都是ThreadPoolTaskExecutor。
实际上,SpringBoot已经考虑到这个问题了,在新的版本中@Async默认使用的线程池就是ThreadPoolTaskExecutor,这里注意看下你使用的版本。
SpringBoot 2.0.9以及之前的版本,使用的线程池默认是SimpleAsyncTaskExecutor,之后默认使用的是ThreadPoolTaskExecutor
3、SpringBoot使用的默认线程池源码解析
SpringBoot默认使用的是哪个线程池,是如何配置的呢?这一部分,感兴趣的可以看下,不感兴趣的直接看下一节,配置线程池参数就可以了。
找到@Async的拦截器方法,每次会先执行。
入参的Excutor参数可以是null,如果不是null,就使用指定的Excutor,这个就可以来指定我们自己的Excutor,这个后面说。
如果入参是null,就使用如下方法获取。这里有三行关键代码,第一行通过beanFactory.getBean获取TaskExcutor类型的Bean。但是这里是通过类型获取的,实现Excutor的如果有多个类,就会报错,被第二行代码捕获,进入第三行代码获取Bean。第三行通过名字获取的Bean,名字是taskExcutor。
名字是taskExcutor的Bean,是SpringBoot默认给我们配置好了的,接着看SpringBoot的自动配置相关源码TaskExecutionAutoConfiguration类,红框里最终配置的Bean名字就是taskExcutor。
这里注意这个注解@ConditionalOnMissingBean,只在没有Excutor实现类的时候,才会默认配置。和上面通过beanFactory.getBean获取TaskExcutor类型的Bean代码呼应起来了。如果我们项目没有注入Excutor的话,SpringBoot就会默认注入ThreadPoolTaskExec开发者_JS开发utor。如果我们指定了,这样使用@Async(“myExcutor”)则使用我们指定的。如果没指定,还有多个实现Bean冲突了,则使用Bean名字是taskExcutor的。
我们再来看下默认线程池配置,创建线程池时使用的都是TaskExecutionProperties这个配置文件。
我们再来看这个配置文件,红框处是几个关键参数。
问题非常明显,有几个参数值最大值非常大,Integer.MAX_VALUE。需要我们在使用时根据业务场景进行配置,不然存在隐患。
4、线程池配置
首先,我们需要了解线程池的基本运行原理,才能更好的配置。这里简单给大家介绍一下,在后面专门的线程池文章会详细说明。
线程池有几个重要的基本参数,核心线程数,等待队列,最大线程数量,线程存活时间等。有新的任务过来时,先启用核心线程,核心线程满了后,再加到等待队列,等待队列也满了后,如果小于最大线程数,则继续开启新的线程,直到达到最大线程数量。
所以,上文线程池默认配置中,系统默认配置的核心线程数是8,但是等待队列最大值无上限,这里就不太合适。需要按照业务实际使用场景来配置。同时最大线程数也需要配置,不然资源不够,起了无限多的线程,就OOM了。
存活时间默认60s,也是根据自己的任务响应时间来配置。allowCoreThreadTimeout是配置核心线程数是否设置存活时间的参数,如果配置true,那么核心线程数存活时间也会生效,空闲时不会一直存在。
配置时可以自己通过代码设置,也可以直接在配置文件设置,下面是简单参考。
spring: task: execution: pool: core-size: 8 queue-capacity: 100 max-size: 16 GAIwMm keep-alive: 60 allow-core-thread-timeout: false
四、结语
到这,我们今天的文章就算结束了,大家跟着文章,应该学会使用异步编程了,能够满足大多数场景的使用了。
当然,还有些许多问题因为篇幅问题这里没提,比如如何写自己的线程池,除去SpringBoot的异步方式,Java8也给我们提供了异步编程方式CompletableFuture,这个如何使用呢?和SpringBoot提供的注解有何区别?
到此这篇关于详解SpringBoot如何开启异步编程的文章就介绍到这了,更多相关SpringBoot异步编程内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!
精彩评论