Spring框架提供的异步执行能力

一、前言

Spring Framework分别使用TaskExecutor和TaskScheduler接口提供异步执行和任务调度的抽象。本节我们着重讲解@Async如何实现异步处理。

二、 @Scheduled

@Scheduled注释可以与触发器元数据一起添加到方法中。例如,以固定延迟每5秒调用以下方法,这意味着将从每个前一次调用的完成时间开始测量该周期,例如:

@Scheduled(fixedDelay=5000)public void doSomething() {    // something that should execute periodically}

如果需要固定费率执行,只需更改注释中指定的属性名称即可。在每次调用的连续开始时间之间测量的每5秒执行以下操作:

@Scheduled(fixedRate=5000)public void doSomething() {    // something that should execute periodically}

如果简单的周期性调度不够表达,则可以提供cron表达式。例如,以下内容仅在工作日执行:

@Scheduled(cron="*/5 * * * * MON-FRI")public void doSomething() {    // something that should execute on weekdays only}

请注意,要调度的方法必须具有void返回,并且不得指望任何参数。如果该方法需要与Application Context中的其他对象进行交互,则通常会通过依赖注入提供这些对象。

需要注意的是该注解默认是不会解析的,需要加上@EnableScheduling 来启动。

@Async

可以在方法上添加@Async注释,以便异步调用该方法。换句话说,调用者将在调用时立即返回,并且该方法的实际执行将发生在Spring TaskExecutor中。

    @Async    public void dosomthingAsync() {
System.out.println("--dosomthingAsync begin---"); // 模拟异步处理 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("--dosomthingAsync end---"); }

如上代码在方法dosomthingAsync上添加了@Async的注解,所以当我们调用dosomthingAsync方法时候,该方法会马上返回。

与使用@Scheduled注解的方法不同,@Async可以有返回值,因为它们将在运行时由调用者以“正常”方式调用,而不是由容器管理的调度任务调用。例如,以下是@Async注解的合法应用程序:

    @Componentpublic class AsyncTask {...    @Async    public CompletableFuture<String> dosomthingAsyncFuture() {
System.out.println("--dosomthingAsync begin---"); CompletableFuture<String> future = new CompletableFuture<String>();
// 模拟异步处理 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } future.complete("ok"); System.out.println("--dosomthingAsync end---");
return future; }}

如上代码调用该方法后,该方法会马上返回一个CompletableFuture对象,如果你一直持有这个CompletableFuture对象,那么等dosomthingAsyncFuture内业务处理异步处理完毕后,就可以从dosomthingAsyncFuture的get()方法获取到执行结果。那么Spring框架是如何做到我们dosomthingAsyncFuture时候会马上返回一个CompletableFuture那?其实其对该类进行了代理,经过代理后的上面的方法类似于:

public class AsynTaskProxy {
public AsyncTask getAsyncTask() { return asyncTask; }
public void setAsyncTask(AsyncTask asyncTask) { this.asyncTask = asyncTask; }
private AsyncTask asyncTask;
private TaskExecutor executor = new SimpleAsyncTaskExecutor();
public CompletableFuture<String> dosomthingAsyncFuture() {
return CompletableFuture.supplyAsync(new Supplier<String>() {
@Override public String get() { try { return asyncTask.dosomthingAsyncFuture().get(); } catch (Throwable e) { throw new CompletionException(e); } } }); }}

Spring会对AsyncTask类使用类似的AsynTaskProxy进行代理,并且会把AsynTask的实例注入到AsynTaskProxy内部,当我们调用AsynTask的dosomthingAsyncFuture方法时候,实际调用的是AsynTaskProxy的dosomthingAsyncFuture方法,后者则使用 CompletableFuture.supplyAsync开启了一个异步任务(其马上返回一个 CompletableFuture对象),并且使用默认的SimpleAsyncTaskExecutor线程池做为异步处理线程,然后异步任务内在具体调用了 AsyncTask实例的dosomthingAsyncFuture方法,并且在返回的future上获取执行结果。

默认情况下,Spring将搜索关联的线程池定义:Spring上下文容器中的唯一的org.springframework.core.task.TaskExecutor类型的bean,如果不存在,则查找名为“taskExecutor”的java.util.concurrent.Executo的 bean。如果两者都不存在,则将使用org.springframework.core.task.SimpleAsyncTaskExecutor的一个实例来处理异步方法调用。

SimpleAsyncTaskExecutor中对每个异步任务对应开启一个线程来进行处理,会造成线程频繁创建与销毁,没有进行线程复用,所以我们可以创建自己的线程池,比如下面:

@Bean    public TaskExecutor bizExecutor() {        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();        executor.setMaxPoolSize(8);        executor.setCorePoolSize(8);        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());        executor.setQueueCapacity(5);
return executor; }

则当bizExecutor通过@Bean注入到Spring上下文中后,异步处理就使用其中线程池进行处理。

需要注意的是该注解默认是不会被解析的,需要加上@EnableAsync来启动。

本文翻译自https://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/scheduling.html#scheduling-annotation-support-async

并融入作者自己理解。

欢迎关注微信公众号:技术原始积累

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/175410.html

(0)
上一篇 2021年10月12日
下一篇 2021年10月12日

相关推荐

发表回复

登录后才能评论