本文转载自微信公众号「六脉神剑的程序人生」,作者六脉神剑小六六。转载本文请联系六脉神剑的程序人生公众号。
絮叨
刚好在读项目代码的时候,发现了WebAsyncTask这个新玩意,给大家来科普科普,不是那么的深入,不喜勿喷!
SpringBoot中同异步调用的使用
异步请求的处理。除了异步请求,一般上我们用的比较多的应该是异步调用。通常在开发过程中,会遇到一个方法是和实际业务无关的,没有紧密性的。比如记录日志信息等业务。这个时候正常就是启一个新线程去做一些业务处理,让主线程异步的执行其他业务。
- 同步请求
- 异步请求
SprinBoot中@Async异步方法
异步的好处是,可以提高程序吞吐量,一个任务,让耗时的异步处理,并继续同步处理后面的任务,异步任务可以返回结果,拿到结果后可结合同步处理过程中的变量一起处理计算
具体的使用
在Spring中,基于@Async标注的方法,称之为异步方法;这些方法将在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。
自定义线程池异步调用
配置@EnableAsync使@Async生效
@SpringBootApplication
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
自定义线程池
@Component
@Scope //单例
public class MyExecutePoll {
@Bean
public Executor myAsyncPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程池大小
executor.setCorePoolSize(20);
//最大线程数
executor.setMaxPoolSize(40);
//队列容量
executor.setQueueCapacity(50);
// 活跃时间
executor.setKeepAliveSeconds(300);
// 线程名字前缀
executor.setThreadNamePrefix("MyExecutor-");
//设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,使异步线程的销毁优先于Redis等其他处理报错
executor.setWaitForTasksToCompleteOnShutdown(true);
//设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
executor.setAwaitTerminationSeconds(60);
// setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务
// CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
使用@Async
@Async("myAsyncPool") //@Async使用默认的线程
public Future<String> doTask() throws Exception {
//业务处理 使用Future返回异步调用结果
return new AsyncResult<>("任务一完成");
- 1.
- 2.
- 3.
- 4.
在Spring中运用 Async注解 需要注意几点:
- AsyncTest.java,测试类,调用异步任务,同时执行同步方法
- OrderService.java,异步任务类,提供异步方法
- AsyncThreadPoolConfig.java,异步任务线程池配置类,配置异步任务运行的线程池大小等
基于Spring实现异步请求
Spring可以通过Callable或者WebAsyncTask等方式实现异步请求, 我们来看看,这2种实现方式!
Callable
Callable是为了异步生成返回值提供基本的支持。简单来说就是一个请求进来,如果你使用了Callable,在没有得到返回数据之前,DispatcherServlet和所有Filter就会退出Servlet容器线程,但响应保持打开状态,一旦返回数据有了,这个DispatcherServlet就会被再次调用并且处理,以异步产生的方式,向请求端返回值。这么做的好处就是请求不会长时间占用服务连接池,提高服务器的吞吐量。
@GetMapping("/callable")
public Callable<String> testCallable() throws InterruptedException {
log.info("主线程开始!");
Callable<String> result = new Callable<String>() {
@Override
public String call() throws Exception {
log.info("副线程开始!");
Thread.sleep(1000);
log.info("副线程结束!");
return "SUCCESS";
}
};
log.info("主线程结束!");
return result;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
输出结果
主线程开始!
主线程结束!
副线程开始!
副线程结束!
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
WebAsyncTask
一个请求到服务上,是用的web容器的线程接收的
我们可以使用WebAsyncTask将这个请求分发给一个新的线程去执行,容器的线程可以去接收其他请求的处理。一旦WebAsyncTask返回数据有了,就会被再次调用并且处理,以异步产生的方式,向请求端返回值,但是其实我觉得前端的请求rt并不会说变短。
/**
* 查询
*/
@RequestMapping(method = RequestMethod.GET, value = "/aysncTask/{testId}")
@ResponseStatus(HttpStatus.OK)
public WebAsyncTask<Response> aysncTask(@PathVariable("testId") String testId) {
System.out.println(String.format("/aysncTask/%s 被调用 thread id is: %s", testId,Thread.currentThread().getName()));
Callable<Response> callable = () -> {
Thread.sleep(1000L);
Response response = new Response(true,"异步执行成功");
System.out.println(String.format("/aysncTask/%s 被调用 thread id is: %s", testId,Thread.currentThread().getName()));
return response;
};
return new WebAsyncTask<Response>(callable);
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
控制台打印如下:在执行业务逻辑之前的线程和具体处理业务逻辑的线程不是同一个,达到了我们的目的。async-customize-1这个前缀是我们自定义的下边会说
/aysncTask/12348567676 被调用 thread id is: http-nio-8084-exec-1
/aysncTask/12348567676 被调用 thread id is: async-customize-1
- 1.
- 2.
其实WebAsyncTask比起Callable是有以下几个优点的
官方有这么一句话,截图给你:
如果我们需要超时处理的回调或者错误处理的回调,我们可以使用WebAsyncTask代替Callable
实际使用中,我并不建议直接使用Callable ,而是使用Spring提供的WebAsyncTask 代替,它包装了Callable,功能更强大些
总结
其实本文就是给大家科普下,一些异步的用法,不至于说看到人家这么用很蒙b,多线程的东西还是优点东西的,大家一起学习。