1.3.5 并发编程
对于任何请求-响应模式下的处理过程,都需要关注请求的响应能力。对于Web请求而言,我们希望服务端能够快速返回处理结果,从而提高系统的响应能力。而Spring框架也针对这一诉求给出了它的解决方案,这就是并发编程模型。Spring对于并发编程模型的支持包括两个方面。一方面,Spring提供了@Async注解来实现通用的异步编程能力,并提供了WebAsyncTask组件来实现面向Web的异步处理机制。另一方面,Spring还提供了两个更高层级的并发编程工具类,即任务执行器TaskExecutor以及任务调度器TaskScheduler。
1. 并发编程的实战内容
(1)Spring Async
异步处理的主要优势是调用方不必等待被调用方完成执行过程,因为可以在一个单独的线程中执行一个方法。我们知道,JDK为开发人员提供了Future、CompletableFuture等支持异步编程的实现工具。而Spring异步编程模型在此基础上提供了一个全新的@Async注解,该注解可以与JDK中的Future机制以及线程池进行无缝整合。
Spring的@Async注解是通用的,我们可以用它来完成针对任意场景的异步处理流程。随着Spring Boot的诞生,特定于Web请求处理过程,也出现了WebAsyncTask这一专门的异步任务组件。相较@Async注解,WebAsyncTask为开发人员提供了更灵活的异步任务处理机制。
请注意,异步编程模型是一个相对复杂的话题,很多开发人员只会简单地使用@Async注解,而不知道其实现原理,这样就可能导致错误地使用该注解。本书将通过分析Spring的源码来深入理解@Async注解背后的实现原理,避免误用。
从实战角度讲,Spring Async机制已经为开发人员提供了良好的开发体验。但是该机制本质上也是对JDK中Future机制的封装和扩展,而随着JDK版本的演进,内置的Future机制也在不断地发展和增强。因此,灵活使用多种Future机制是一种开发技巧。同时,异步编程的背后是对系统线程资源的利用,所以合理设置线程池也是开发人员需要注意的一个实战点。
(2)Spring任务执行器和调度器
JDK中自带的执行器(Executor)组件使用起来非常方便,本质上是在所有内部线程管理任务上提供了一个抽象层。而Spring提供TaskExecutor接口作为Executor的扩展,包含了一大批非常有用的实现类。我们会在介绍这些实现类基本功能的同时,给出对TaskExecutor运行原理的分析。
有时候,我们需要以固定的时间间隔执行任务,这就需要引入任务调度的概念。Spring专门针对任务调度场景提供了TaskScheduler组件。基于Spring,我们将看到如何使用@Scheduled注解来对任务进行调度。同样,我们也将对该注解的实现原理进行讲解。
从实战角度讲,Spring框架分别针对不同的应用场景提供了SimpleAsyncTaskExecutor、SyncTaskExecutor、ConcurrentTaskExecutor、ThreadPoolTaskExecutor等TaskExecutor。根据具体需求,灵活使用多种TaskExecutor是一项开发技巧。同时,Spring框架中的@Scheduled注解功能也十分强大,我们可以通过使用各种表达式来制定不同的调度策略。
2. 并发编程的实战案例
针对Spring的并发编程能力,我们将给出两个面向不同应用场景的案例分析,即Web异步处理案例分析、基于代理的异步执行模型案例分析。
Spring的@Async注解提供的是一种通用的异步编程模式,可以通过@EnableAsync注解来启用该功能。而针对Web异步处理需求和场景,我们可以使用WebAsyncTask组件,该组件为开发人员提供了更灵活的异步任务处理流程,并内置了异步回调、超时处理和异常处理机制。我们将通过模拟异步任务执行超时等异常场景来演示WebAsyncTask的各项使用技巧。
Spring的@Async注解本质上是基于动态代理机制实现的,本书也会对这一实现机制进行详细讨论。而在解释@Async注解的具体原理之前,我们通过一个基于代理的异步编程模型案例来实现一套自定义的、类似@Async注解的异步执行组件,以便更好地理解Spring框架中与异步执行模型相关的设计思想和实现机制。
TaskExecutor和TaskScheduler这两个工具组件的使用都比较简单,我们会在这两个案例中穿插对它们的使用方式的具体描述。