SpringBoot中Async异步方法和定时任务介绍

1.功能说明

Spring提供了Async注解来实现方法的异步调用。 即当调用Async标识的方法时,调用线程不会等待被调用方法执行完成即返回继续执行以下操作,而被调用的方法则会启动一个独立线程来执行此方法

这种异步执行的方式通常用于处理接口中不需要返回给用户的数据处理。比如当注册的时候,只需要将用户信息返回用户,而关于信息的保存操作可以使用异步执行。

Spring提供了Scheduled注解来实现定时任务的功能。

在异步方法和定时任务功能中都是开发这自己定义需要执行的方法,然后交给Spring容器管理线程,并执行相应的方法。在使用异步方法和定时任务的时候需要特别注意的是线程池的配置以及任务中异常的处理。下面对这两个功能进行简单介绍。

2.关键注解和配置接口

功能开启注解:

EnableAsync和EnableScheduling

通过在Spring的配置类中添加这两个注解来开启Spring的异步方法和定时任务的功能

异步方法标识注解Async,其定义为:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {

    String value() default "";

}

在注解定义中可以看到此注解可以用于type和method,当此注解用于类的时候,表示此类中的所有方法都为异步方法。此注解中的value属性可用于指定执行此异步方法的线程池。线程池的具体确定方法下面具体分析。

定时任务标识注解Scheduled,定义如下:

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
    定时任务
    String cron() default "";

    String zone() default "";
    //上次执行结束到下次执行开始
    long fixedDelay() default -1;
    
    String fixedDelayString() default "";
    上次执行开始到本次执行开始
    long fixedRate() default -1;

    fixedRate或者fixedDelay的时候第一次的延迟时间
    long initialDelay() default -1;

}

3.Spring线程池的选择和自定义配置线程池

在项目中我们通常不会自己手动创建线程,而是通过统一的线程池来执行task或者异步方法,使用这种方法来避免多人团队中由于自定义线程导致的资源耗尽的问题。在自定义线程池之前首先要了解Spring在执行异步任务或者方法的时候是怎么选择线程池的。

3.1 Async对于线程池的选择顺序

Async线程池的选择顺序如下图所示:

Spring在执行async标识的异步方法的时候首先会在Spring的上下文中搜索类型为TaskExecutor或者名称为“taskExecutor”的bean,当可以找到的时候,就将任务提交到此线程池中执行。当不存在以上线程池的时候,Spring会手动创建一个SimpleAsyncTaskExecutor执行异步任务。

另外当标识async注解的时候指定了,value值,Spring会使用指定的线程池执行。比如以下:

@Async(value = "asyncTaskThreadPool")

这个时候Spring会去上下文中找名字为asyncTaskThreadPool的bean,并执行异步任务,找不到,会抛出异常。

3.2 Scheduled对于线程池的选择顺序

Scheduled对于线程池的选择顺序如下图所示:

当Spring执行定时任务的时候,首先会在上下文中找类型为TaskScheduler或者名称为taskScheduler的bean,找不到的时候会手动创建一个线程执行此task。

3.3 自定义线程池和异常处理

在了解了Spring对于线程池的选择后,我们需要自定义线程池。自定义Async线程池有三种方式。

方法一:首先配置接口,重写获取线程池的方法。

配置Async方法的线程池需要继承AsyncConfigurerSupport类,或者实现AsyncConfigurer接口,并重写getAsyncExecutor方法,代码如下:

@Configuration
@EnableAsync
public class ThreadPoolBeanFactory extends AsyncConfigurerSupport{

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor asyncTaskThreadPool = new ThreadPoolTaskExecutor();
        asyncTaskThreadPool.setCorePoolSize(100);
        asyncTaskThreadPool.setMaxPoolSize(200);
        asyncTaskThreadPool.setQueueCapacity(11);
        asyncTaskThreadPool.setThreadFactory(new ThreadFactory() {

            private final AtomicLong index = new AtomicLong(1);
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "Async-override-task-pool-thread-" + index.getAndIncrement());
            }
        });
        asyncTaskThreadPool.initialize();
        return asyncTaskThreadPool;
    }


    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        //返回值为void的异步方法不会传递异常,当方法中出现异常的时候只会打印日志,重写此方法来自定义异常处理方法
        return null;
    }
}

这种定义的方法缺点是没有定义bean。

方法二:自定义相应类型的线程池bean。

第二种方法是基于Spring对线程选择的原理来实现的,定义一个类型为TaskExecutor的bean,定义方式如下:

    @Bean
    public TaskExecutor asyncTaskThreadPool() {

        ThreadPoolTaskExecutor asyncTaskThreadPool = new ThreadPoolTaskExecutor();
        asyncTaskThreadPool.setCorePoolSize(100);
        asyncTaskThreadPool.setMaxPoolSize(200);
        asyncTaskThreadPool.setThreadFactory(new ThreadFactory() {

            private final AtomicLong index = new AtomicLong(1);
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "Async-task-pool-thread-" + index.getAndIncrement());
            }
        });
       // asyncTaskThreadPool.initialize();//当为bean的时候不需要调用此方法,装载容器的时候回自动调用
        return asyncTaskThreadPool;
    }

以上两种方式定义线程池的时候在定义异步方法可以不执行线程池。定义如下:

    @Async
    public void test(){
        System.out.println(Thread.currentThread().getName());
        
    }

此时Spring会自动使用以上定义的线程池执行此方法。使用以上两种配置输出结果依次是:

Async-task-pool-thread-1
Async-task-override-pool-thread-1

方法三 在Async注解中执行线程池名称

异步任务定义如下:

    @Async(value = "asyncTaskThreadPool")
    public void asyncTask2() {
        LOGGER.info("AsyncTask2 start.");
        LOGGER.info(Thread.currentThread().getName());
        LOGGER.info("AsyncTask2 finished.");
    }

此时Spring会在上下文中找名称为asyncTaskThreadPool的线程池来执行此任务。

类似的可以自定义Scheduled的线程池,需要实现的配置接口为:SchedulingConfigurer。方法类似。

4.Async返回操作结果

异步任务可以通过定义返回类型为Future来实现返回值,定义如下:

    @Async
    public Future<String> asyncTaskWithResult() {
        LOGGER.info("AsyncTaskWithResult start.");
        try {
            Thread.sleep(1000 * 10);
        } catch (Exception e) {
            return new AsyncResult<>("error" + e.getMessage());
        }
        LOGGER.info("AsyncTaskWithResult finished.");

        return new AsyncResult<>("success");
    }

5.编写单元测试测试功能

单元测试代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class AsyncApplicationTests {

    @Autowired
    private AsyncTaskService asyncTaskService;

    @Test
    public void asyncTest() throws Exception{
        Future<String> future = asyncTaskService.asyncTaskWithResult();

        while (!future.isDone()) {
            System.out.println("Wait asyncTaskWithResult.");
            Thread.sleep(1000);
        }
        System.out.println("asyncTaskWithResult result is:" + future.get());
        System.out.println("asyncTask finished.");

    }

}

输出内容如下:

Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
Wait asyncTaskWithResult.
AsyncTaskWithResult finished.
asyncTaskWithResult result is:success
asyncTask finished.

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏木木玲

Netty 那些事儿 ——— 关于 “Netty 发送大数据包时 触发写空闲超时” 的一些思考

7386
来自专栏JavaEdge

Spring 5.0中文版-3.9

3499
来自专栏JMCui

再学习之Spring(依赖注入).

一、概述     Spring框架是以 简化Java EE应用程序的开发 为目标而创建的。Spring可以实现很多功能,但是这些功能的底层都依赖于它的两个核心特...

3776
来自专栏coderhuo

虚拟内存探究 -- 第三篇:一步一步画虚拟内存图

这是虚拟内存系列文章的第三篇。 前面我们提到在进程的虚拟内存中可以找到哪些东西,以及在哪里去找。 本文我们将通过打印程序中不同元素内存地址的方式,一步一步细...

2484
来自专栏平凡文摘

面试问烂的 Spring AOP 原理、SpringMVC 过程

Spring AOP ,SpringMVC ,这两个应该是国内面试必问题,网上有很多答案,其实背背就可以。但今天笔者带大家一起深入浅出源码,看看他的原理。以期让...

1472
来自专栏程序猿DD

Spring Boot使用@Async实现异步调用:自定义线程池

在之前的Spring Boot基础教程系列中,已经通过《Spring Boot中使用@Async实现异步调用》一文介绍过如何使用 @Async注解来实现异步调用...

6638
来自专栏JackieZheng

学习SpringMVC——说说视图解析器

  各位前排的,后排的,都不要走,咱趁热打铁,就这一股劲我们今天来说说spring mvc的视图解析器(不要抢,都有位子~~~)   相信大家在昨天那篇如何获取...

26010
来自专栏极客编程

用Java为Hyperledger Fabric(超级账本)开发区块链智能合约链代码之部署与运行示例代码

您已经定义并启动了本地区块链网络,而且已构建 Java shim 客户端 JAR 并安装到本地 Maven 存储库中,现在已准备好在之前下载的 Hyperled...

2521
来自专栏菩提树下的杨过

JAVA CDI 学习(1) - @Inject基本用法

CDI(Contexts and Dependency Injection 上下文依赖注入),是JAVA官方提供的依赖注入实现,可用于Dynamic Web M...

2182
来自专栏移动开发

利用Gson将对象转换成json,忽略某些字段的方法

有时我们将对象转换成json格式的字符串时,并不希望里面所有的字段都转换,我们可能希望忽略某些.默认的gson是全部转换的,为此要给gson做一些配置.其实Gs...

5333

扫码关注云+社区

领取腾讯云代金券