共计 5659 个字符,预计需要花费 15 分钟才能阅读完成。
引言
说到异步大家必定首先会先想到同步。咱们先来看看什么是同步?
所谓 同步 ,就是收回一个性能调用时,在没有失去后果之前,该调用就不返回或继续执行后续操作。
简略来说,同步就是必须一件一件事做,等前一件做完了能力做下一件事。
异步:异步就相同,调用在收回之后,这个调用就间接返回了,不须要等后果。
浏览器同步
浏览器发动一个 request 而后会始终待一个响应 response,在这期间外面它是阻塞的。比方晚期咱们在咱们在逛电商平台的时候买货色咱们关上一个商品的页面,大抵流程是不是可能是这样,每次关上一个页面都是由一个线程从头到尾来解决,这个申请须要进行数据库的拜访须要把商品价格库存啥的返回页面,还须要去调用第三方接口,比方优惠券接口等咱们只有等到这些都解决实现后这个线程才会把后果响应给浏览器,在这等后果期间这个线程只能始终在干等着啥事件也不能干。这样的话是不是会有有肯定的性能问题。大抵的流程如下:
浏览器异步
为了解决下面同步阻塞的问题,再 Servlet3.0 公布后,提供了一个新个性:异步解决申请。比方咱们还是进入商品详情页面,这时候这个前端发动一个申请,而后会有一个线程来执行这个申请,这个申请须要去数据库查问库存、调用第三方接口查问优惠券等。这时候这个线程就不必干等着呢。它的工作到这就实现了,又能够执行下一个工作了。等查询数据库和第三方接口查问优惠券有后果了,这时候会有一个新的线程来把处理结果返回给前端。这样的话线程的工作量是不超级饱和,须要不停的干活,连劳动的机会都不给了。
- 这个异步是纯后端的异步,对前端是无感的,异步也并不会带来响应工夫上的优化,原来该执行多久照样还是须要执行多久。然而咱们的申请线程(Tomcat 线程)为异步 servlet 之后,咱们能够立刻返回,依赖于业务的工作用业务线程来执行,也就是说,Tomcat 的线程能够立刻回收,默认状况下,Tomcat 的外围线程是 10,最大线程数是 200, 咱们能及时回收线程,也就意味着咱们能解决更多的申请,可能减少咱们的吞吐量,这也是异步 Servlet 的次要作用。
上面咱们就来看看 Spring mvc 的几种异步形式吧
https://docs.spring.io/spring…
在这个之前咱们还是先简略的回顾下 Servlet 3.1 的异步:
- 客户端(浏览器、app)发送一个申请
- Servlet 容器调配一个线程来解决容器中的一个 servlet
- servlet 调用 request.startAsync()开启异步模式,保留 AsyncContext, 而后返回。
- 这个 servlet 申请线程以及所有的过滤器都能够完结,但其响应(response)会期待异步线程解决完结后再返回。
- 其余线程应用保留的 AsyncContext 来实现响应
- 客户端收到响应
Callable
/** 公众号:java 金融
* 应用 Callable
* @return
*/
@GetMapping("callable")
public Callable<String> callable() {System.out.println(LocalDateTime.now().toString() + "---> 主线程开始");
Callable<String> callable = () -> {
String result = "return callable";
// 执行业务耗时 5s
Thread.sleep(5000);
System.out.println(LocalDateTime.now().toString() + "---> 子工作线程("+Thread.currentThread().getName()+")");
return result;
};
System.out.println(LocalDateTime.now().toString() + "---> 主线程完结");
return callable;
}
public static String doBusiness() {
// 执行业务耗时 10s
try {Thread.sleep(10000);
} catch (InterruptedException e) {e.printStackTrace();
}
return UUID.randomUUID().toString();
}
- 控制器先返回一个 Callable 对象
- Spring MVC 开始进行异步解决,并把该 Callable 对象提交给另一个独立线程的执行器 TaskExecutor 解决
- DispatcherServlet 和所有过滤器都退出 Servlet 容器线程,但此时办法的响应对象仍未返回
- Callable 对象最终产生一个返回后果,此时 Spring MVC 会从新把申请分派回 Servlet 容器,复原解决
- DispatcherServlet 再次被调用,复原对 Callable 异步解决所返回后果的解决
下面就是 Callable 的一个执行流程,上面咱们来简略的剖析下源码,看看是怎么实现的:
咱们晓得 SpringMvc 是能够返回 json 格局数据、或者返回视图页面(html、jsp)等,SpringMvc 是怎么实现这个的呢?最次要的一个外围类就是 org.springframework.web.method.support.HandlerMethodReturnValueHandler 咱们来看看这个类,这个类就是一个接口,总共就两个办法;
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
下面这个咱们的申请是返回 Callable<String> 这样一个后果的,咱们会依据这个返回的类型去找所有实现了 HandlerMethodReturnValueHandler 这个接口的实现类,最终咱们会依据返回类型通过 supportsReturnType 这个实现的办法找到一个对应的 HandlerMethodReturnValueHandler 实现类,咱们依据返回类型是 Callable 而后就找到了实现类 CallableMethodReturnValueHandler。
开启异步线程的话也就是在 handleReturnValue 这个办法外面了,感兴趣的大家能够入手去 debug 下还是比拟好调试的。
CompletableFuture 和 ListenableFuture
@GetMapping("completableFuture")
public CompletableFuture<String> completableFuture() {
// 线程池个别不会放在这里,会应用 static 申明,这只是演示
ExecutorService executor = Executors.newCachedThreadPool();
System.out.println(LocalDateTime.now().toString() + "---> 主线程开始");
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(IndexController::doBusiness, executor);
System.out.println(LocalDateTime.now().toString() + "---> 主线程完结");
return completableFuture;
}
@GetMapping("listenableFuture")
public ListenableFuture<String> listenableFuture() {
// 线程池个别不会放在这里,会应用 static 申明,这只是演示
ExecutorService executor = Executors.newCachedThreadPool();
System.out.println(LocalDateTime.now().toString() + "---> 主线程开始");
ListenableFutureTask<String> listenableFuture = new ListenableFutureTask<>(()-> doBusiness());
executor.execute(listenableFuture);
System.out.println(LocalDateTime.now().toString() + "---> 主线程完结");
return listenableFuture;
}
注:这种形式记得不要应用内置的不要应用内置的 ForkJoinPool 线程池,须要本人创立线程池否则会有性能问题
WebAsyncTask
@GetMapping("asynctask")
public WebAsyncTask asyncTask() {SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
System.out.println(LocalDateTime.now().toString() + "---> 主线程开始");
WebAsyncTask<String> task = new WebAsyncTask(1000L, executor, ()-> doBusiness());
task.onCompletion(()->{System.out.println(LocalDateTime.now().toString() + "---> 调用实现");
});
task.onTimeout(()->{System.out.println("onTimeout");
return "onTimeout";
});
System.out.println(LocalDateTime.now().toString() + "---> 主线程完结");
return task;
}
DeferredResult
@GetMapping("deferredResult")
public DeferredResult<String> deferredResult() {System.out.println(LocalDateTime.now().toString() + "---> 主线程 ("+Thread.currentThread().getName()+") 开始");
DeferredResult<String> deferredResult = new DeferredResult<>();
CompletableFuture.supplyAsync(()-> doBusiness(), Executors.newFixedThreadPool(5)).whenCompleteAsync((result, throwable)->{if (throwable!=null) {deferredResult.setErrorResult(throwable.getMessage());
}else {deferredResult.setResult(result);
}
});
// 异步申请超时时调用
deferredResult.onTimeout(()->{System.out.println(LocalDateTime.now().toString() + "--->onTimeout");
});
// 异步申请实现后调用
deferredResult.onCompletion(()->{System.out.println(LocalDateTime.now().toString() + "--->onCompletion");
});
System.out.println(LocalDateTime.now().toString() + "---> 主线程 ("+Thread.currentThread().getName()+") 完结");
return deferredResult;
}
- 下面这几种异步形式都是会等到业务 doBusiness 执行完之后(10s)才会把 response 给到前端,执行申请的主线程会立刻完结,响应后果会交给另外的线程来返回给前端。
- 这种异步跟上面的这个所谓的假异步是不同的,这种状况是由主线程执行实现之后立马返回值(主线程)给前端,不会等个 5s 在返回给前端。
@GetMapping("call")
public String call() {new Thread(new Runnable() {
@Override
public void run() {
try {Thread.sleep(5000);
} catch (InterruptedException e) {e.printStackTrace();
}
}
}).start();
return "这是个假异步";
}
这几种异步形式都跟返回 Callable 差不多,都有对应的 HandlerMethodReturnValueHandler 实现类,无非就是丰盛了本人一些非凡的 api、比方超时设置啥的,以及线程池的创立是谁来创立,执行流程根本都是一样的。
总结
- 理解 spring mvc 的异步编程,对咱们后续学习响应式编程、rxjava、webflux 等都是有益处的。
- 异步编程能够帮咱们高效的利用系统资源。
完结
- 因为本人满腹经纶,难免会有纰漏,如果你发现了谬误的中央,还望留言给我指出来, 我会对其加以修改。
- 如果你感觉文章还不错,你的转发、分享、赞叹、点赞、留言就是对我最大的激励。
- 感谢您的浏览, 非常欢送并感谢您的关注。
站在伟人的肩膀上摘苹果:
https://blog.csdn.net/f641385…