专栏首页Java后端技术栈cwnaitSpring框架提供的异步执行能力

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

一、前言

Spring Framework分别使用TaskExecutor和TaskScheduler接口提供异步执行和任务调度的抽象。Spring还具有支持线程池或在应用程序服务器环境中委托给CommonJ的接口的实现。最终,在公共接口背后使用这些实现抽象出了Java SE 5,Java SE 6和Java EE环境之间的差异。本节我们着重讲解@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来启动。

本文分享自微信公众号 - Java后端技术栈(t-j20120622),作者:加多

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java 线程池 8 大拒绝策略,面试必问!

    谈到java的线程池最熟悉的莫过于ExecutorService接口了,jdk1.5新增的java.util.concurrent包下的这个api,大大的简化了...

    用户4143945
  • Spring Aop之Cglib实现原理详解

    Spring Aop实现对目标对象的代理,主要有两种方式:Jdk代理和Cglib代理。这两种代理的区别在于,Jdk代理与目标类都会实现同一个接口,并且在代理...

    用户4143945
  • Java中的锁原理、锁优化、CAS、AQS,看这篇就对了!

    Java编程语言允许线程访问共享变量, 为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些...

    用户4143945
  • JVM 中的守护线程

    在之前的《详解JVM如何处理异常》提到了守护线程,当时没有详细解释,所以打算放到今天来解释说明一下JVM守护线程的内容。

    技术小黑屋
  • Java描述设计模式(05):原型模式

    知了一笑
  • 【Chromium中文文档】Chromium如何展示网页

    这个文档从底层描述了Chromium是如何展示网页的。请确认你已经读过多进程架构这篇文章。你会特别想要了解主要组件的框架。你也可能对多进程资源加载感兴趣,以了解...

    梦里茶
  • Apache commons-pool对象池妙用

    大多时候,我们获取对象的方法都是直接new一个。但是,对于大对象的构造,或者构造耗时比较久的对象,我们每次要使用都去new一个是很不科学的。比如数据库的连接对象...

    春哥大魔王
  • VAE的三种不同推导方法

    我们将VAE与GAN都可以放到分布拟合的框架下考虑,简单说,就是有一个未知的真实数据分布,我们想要表示该分布。而VAE最精髓的步骤之一,就是使用隐变量,将数据分...

    用户1908973
  • 并发编程之信号量

    爱撒谎的男孩
  • [设计模式]之六:桥接模式

    这个原则的好处是,优先使用对象的合成/聚合将有助于你保持每个类被封禁,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大...

    wOw

扫码关注云+社区

领取腾讯云代金券