之前我们简要说过@Async
和@Scheduled
的用法,这俩注解会帮你完成异步任务和定时任务的需求。不知道你有没有想过,这些异步任务和定时任务都是在哪个线程执行的?Spring Boot肯定在背后做了很多工作,本文就来说说框架都为我们做了什么。
首先肯定是有线程池的。Spring Boot已经帮你创建并配置好了,还配了两个,一个供@Async
使用,一个供@Scheduled
使用。
Spring将异步任务和定时任务的执行,抽象出了两个接口,TaskExecutor
和TaskScheduler
。我们先来说说TaskExecutor
。

如果你对Java的线程池相关的API比较熟,那么在需要使用线程池的场景,你可能会用Executors
来生成ExecutorService
(继承于Executor
),从而执行任务。Spring中的TaskExecutor
其实和Java的Executor
是一样的,只不过后者是在Java 5的时候被引入的,Spring为了兼容之前的Java版本,自己搞了一套。时至今日,Spring已经最低要求Java 8了,但是为了兼容之前的Spring版本,还是保持了自己的API,基础的TaskExecutor
接口已经直接继承于Executor
了(如上图)。
TaskExecutor
有很多实现,比如SyncTaskExecutor
会在调用者的当前线程同步执行任务;比如SimpleAsyncTaskExecutor
会针对每个任务新建一个线程,运行完线程就停止;比如你可以通过ConcurrentTaskExecutor
,将Executor
对象包装成TaskExecutor
对象,这样将Java的Executor
对象纳入到Spring管理,方便使用;再比如最常用的ThreadPoolTaskExecutor
,内部有个线程池,任务扔进去运行就完了。实际上ThreadPoolTaskExecutor
封装了Java的ThreadPoolExecutor
,封装的同时也会对其进行一些配置。
Spring Boot会帮你自动生成一个ThreadPoolTaskExecutor
,进行了默认配置,相关代码在TaskExecutionAutoConfiguration
。使用如下属性,还可以对线程池进行自定义,比如核心线程数目、最多线程数目、线程名前缀等等:

如果你的代码里需要ThreadPoolTaskExecutor
,直接通过@Autowired
引入就行了。而@Async
注解用的正是Spring Boot自动生成的这个对象,具体一点说,就是它会去容器里找TaskExecutor
类型的Bean,如果有多个,他会再去找名为“taskExecutor”、类型为Executor
的Bean。我在源码里扒拉半天才搞清楚的,如果也想搞清楚,可以查看AsyncExecutionAspectSupport
的getDefaultExecutor
方法。
下面再来说说TaskScheduler
。

TaskScheduler
倒是没有什么历史遗留问题,你看看上图中的它的方法,会发现,这不跟@Scheduled
注解的参数是大致对应的嘛,简直太好理解了:

我们上面说了,定时任务也有一个对应的线程池,具体实现由ThreadPoolTaskScheduler
负责的,它其实是封装了Java里的ScheduledExecutorService
。并且也有对应的属性方便你去自定义:

如果你研究透了之后会发现,以上其实就是典型的Spring Boot的特点,“自动配置好,不够你自己改”。你通过@EnableAsync
和@EnableScheduling
来开启功能,然后就可以通过@Async
和@Scheduled
来执行异步任务和定时任务,这些任务会分别扔给容器里的ThreadPoolTaskExecutor
和ThreadPoolTaskScheduler
,然后放到各自的线程池中运行,想要自定义可以在application.properties中修改属性,如果还觉得不够用,甚至可以给容器提供自己的TaskExecutor
和TaskScheduler
实现来覆盖Spring Boot默认给你的对象。如果你只想自定义@Async
使用的线程池,可以通过AsyncConfigurer
提供一个自己的Executor
实现。这样一步步由浅入深,由简单场景过渡到复杂场景,各种需求都可以满足。如果有人还不理解Spring Boot的特点到底是啥,你可以拿本文的例子来讲。
再说说@Async
的异常处理。如果你的@Async
方法的返回值是Future
类型或者ListenableFuture
或者CompletableFuture
等类型,那么你在调用Future.get()
方法的时候,异常会被抛出。如果你的@Async
方法的返回值是void,那么你可以通过AsyncConfigurer
传递一个全局的AsyncUncaughtExceptionHandler
对象用来处理异常,它会跟你的任务在同一线程执行。
还有几个类,Spring文档并没有给出怎么用,但是源码告诉了大家怎么用。
比如TaskExecutorCustomizer
,你的ThreadPoolTaskExecutor
在创建完成并通过application.properties做了定制之后,还可以通过TaskExecutorCustomizer
进行进一步的定制。对应的,TaskSchedulerCustomizer
还可以对ThreadPoolTaskScheduler
进行定制。比如TaskDecorator
,可以对放到ThreadPoolTaskExecutor
里执行的Runnable
进行封装。你需要做的就是实现TaskExecutorCustomizer
、TaskSchedulerCustomizer
或者TaskDecorator
,并通过@Bean
方法或者@Component
注解,将他们放到容器中去。
PS:我写完之后反复读了本文,感觉如果不跟着源码一起看的话,很可能不能很好理解。源码和概念相结合,使用效果更佳。
注意:本文归作者所有,未经作者允许,不得转载