首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Spring Boot启用异步线程

Spring Boot启用异步线程

作者头像
itlemon
发布于 2020-04-03 10:05:35
发布于 2020-04-03 10:05:35
2.5K00
代码可运行
举报
文章被收录于专栏:深入理解Java深入理解Java
运行总次数:0
代码可运行

一般的后台管理系统都有导出报表的功能,对于大数据量的报表导出,通常比较耗时,比如管理员点击一个导出按钮,往往要等待很长的时间直到报表成功导出才可以进行下一步操作,显然这种同步的方式已经满足不了需求了。现在实际开发中常用的方式是采用JMS消息队列方式,发送消息到其他的系统中进行导出,或者是在项目中开启异步线程来完成耗时的导出工作。本文将结合报表导出的场景,来讲解一些Spring Boot中如何开启异步线程。

定义线程池和开启异步可用

Spring中存在一个接口AsyncConfigurer接口,该接口就是用来配置异步线程池的接口,它有两个方法,getAsyncExecutorgetAsyncUncaughtExceptionHandler,第一个方法是获取一个线程池,第二个方法是用来处理异步线程中发生的异常。它的源码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.springframework.scheduling.annotation;

import java.util.concurrent.Executor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.lang.Nullable;

public interface AsyncConfigurer {

	// 获取线程池
	@Nullable
	default Executor getAsyncExecutor() {
		return null;
	}

	// 异步异常处理器
	@Nullable
	default AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
		return null;
	}
}

这里的接口提供的都是空实现,所以想要开启异步线程机制,那么就需要我们手动实现这个接口,将实现该接口的类标注为Spring的配置类,那么就开启了Spring的异步可用,那么Spring就会通过getAsyncExecutor来获取一个可用的线程来执行某项异步操作,当然,整个异步的开启还需要结合两个注解,一个是@EnableAsync,另外一个是@Async,第一个是标注在配置类中,用来告诉Spring异步可用,第二个注解通常标注在某个方法中,当调用这个方法的时候,就会从线程池中获取新的线程来执行它。 现在我们来定义线程池并开启异步可用,这里写一个配置类AsyncConfig来实现AsyncConfigurer,代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package cn.itlemon.springboot.async.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

/**
 * @author jiangpingping
 * @date 2018/10/30 19:28
 */
@Configuration
@EnableAsync
@Slf4j
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        // 自定义线程池
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        // 核心线程数
        taskExecutor.setCorePoolSize(10);
        // 最大线程数
        taskExecutor.setMaxPoolSize(30);
        // 线程队列最大线程数
        taskExecutor.setQueueCapacity(2000);
        // 初始化线程池
        taskExecutor.initialize();
        return taskExecutor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> {
            log.error("Error Occurs in async method:{}", ex.getMessage());
        };
    }
}

第一个方法我们定义了一个线程池,并设置了一些基本参数,比如核心线程数、最大线程数、线程队列最大线程数等,第二个方法是处理异步线程中发生的异常,它是一个异常处理器,返回AsyncUncaughtExceptionHandler接口的实现类对象,由于AsyncUncaughtExceptionHandler是一个函数式接口(只有一个抽象方法的接口,通常使用@FunctionalInterface注解标注的接口),所以这里使用了Lambda表达式来简写它的实现类对象,这里的异步异常处理就是记录一下日志,并没有做其他的逻辑操作,如果对Lambda表达式不熟悉,也可以直接使用匿名内部类的方式来创建AsyncUncaughtExceptionHandler的实现类对象,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {

    return new AsyncUncaughtExceptionHandler() {
        @Override
        public void handleUncaughtException(Throwable ex, Method method, Object... params) {
            log.error("Error Occurs in async method:{}", ex.getMessage());
        }
    };
}

需要注意的一点的是,我们在上面的配置类中加入了@EnableAsync注解,那么在Spring注册该配置类为Spring Bean的时候,就会开启异步可用机制。

测试异步可用机制

写一个Service层接口,用来表明生成报表:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package cn.itlemon.springboot.async.service;

import java.util.concurrent.Future;

/**
 * @author jiangpingping
 * @date 2018/10/30 19:32
 */
public interface AsyncService {

    /**
     * 模拟生成报表的异步方法
     */
    void generateReport();

}

它的实现类是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package cn.itlemon.springboot.async.service.impl;

import cn.itlemon.springboot.async.service.AsyncService;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import java.util.concurrent.Future;

/**
 * @author jiangpingping
 * @date 2018/10/30 19:33
 */
@Service
public class AsyncServiceImpl implements AsyncService {

    @Override
    @Async
    public void generateReport() {
        // 模拟异步生成报表代码,这里设置为打印
        System.out.println("报表线程名称:【" + Thread.currentThread().getName() + "】");
    }
    
}

这里假设进行了报表的导出工作,所以使用打印语句来进行简单的模拟,并在方法中标注了@Async注解,那么当调用该方法的时候,Spring会获取一个新的线程来执行这个方法,所以这里打印出执行当前方法的线程名称。我们在写一个控制器,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package cn.itlemon.springboot.async.controller;

import cn.itlemon.springboot.async.service.AsyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * @author jiangpingping
 * @date 2018/10/30 19:36
 */
@RestController
@RequestMapping("/async")
@Slf4j
public class AsyncController {

    private final AsyncService asyncService;

    @Autowired
    public AsyncController(AsyncService asyncService) {
        this.asyncService = asyncService;
    }

    @GetMapping("/page")
    public String asyncPage() {
        System.out.println("当前请求线程名称为:【" + Thread.currentThread().getName() + "】");
        // 异步调用
        asyncService.generateReport();
        // 返回结果
        return "async";
    }
    
}

我们在当前Controller方法中也打印了当前的线程,运行项目,访问指定的URL,就可以对比在调用generateReport方法的时候是否启用了新的线程。我们启动Spring Boot应用,在浏览器地址栏输入:http://localhost:8080/async/page,在控制台打印的结果是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
当前请求线程名称为:【http-nio-8080-exec-1】
报表线程名称:【ThreadPoolTaskExecutor-1

很明显,这不是同一个线程,说明我们开启异步线程成功。

处理异步线程中的异常

一般在Spring中处理异步线程异常分成两类,一类是异步方法没有返回值,另一类是异步方法有返回值。

第一类无返回值方法

对于第一类无返回值情况,我们已经在AsyncConfig配置类中进行了配置,即实现getAsyncUncaughtExceptionHandler方法,也就是当异步线程中的代码发生了异常,就会调用这个方法来进行异常处理,为了检验,我们在AsyncServiceImpl的方法generateReport中手动加一行代码System.out.println(1 / 0);,从而导致其出除零异常,代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
@Async
public void generateReport() {
    // 模拟异步生成报表代码,这里设置为打印
    System.out.println("报表线程名称:【" + Thread.currentThread().getName() + "】");
    System.out.println(1 / 0);
}

当再次启动Spring Boot应用,在浏览器地址栏输入:http://localhost:8080/async/page,那么将在异步流程中发生异常,由于是在不同线程中发生的异常,所以它并不会影响主线程的执行,且发生异常后,由配置了getAsyncUncaughtExceptionHandler方法,那么该异常将会被处理,处理的方式就是使用日志进行了记录:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
2018-10-31 10:57:09.952 ERROR 2391 --- [lTaskExecutor-1] c.i.springboot.async.config.AsyncConfig  : Error Occurs in async method:/ by zero
第二类有返回值方法

对于第二种情况,即异步方法会有返回值,那么我们如何获取到异步线程处理后的返回值呢,通常的方法是将异步方法的返回值使用接口FutureListenableFuture或者类AsyncResult进行包装,即将返回值作为泛型传入到上述接口或者类中。这里我们来简要分析一下它们的源码中的常用方法。

Future接口:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface Future<V> {

	boolean cancel(boolean mayInterruptIfRunning);
	
	boolean isCancelled();
	
	boolean isDone();
	
	V get() throws InterruptedException, ExecutionException;
	
	V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

方法分析:

  • cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunningtrue还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunningtrue还是false,肯定返回true
  • isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回true
  • isDone方法表示任务是否已经完成,若任务完成,则返回true
  • get方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
  • get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null

ListenableFuture接口:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface ListenableFuture<T> extends Future<T> {

	void addCallback(ListenableFutureCallback<? super T> callback);

	void addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback);

	default CompletableFuture<T> completable() {
		CompletableFuture<T> completable = new DelegatingCompletableFuture<>(this);
		addCallback(completable::complete, completable::completeExceptionally);
		return completable;
	}
}

ListenableFuture继承了Future接口,它还额外添加了三个方法,主要用来添加异步现场的回调,可以用来处理异常和获取异步方法的返回值的。AsyncResult类实现了ListenableFuture接口,也实现了它所有的方法。接下来,我们将分别介绍如何获取异步处理后的返回值和异常处理。

使用Future接口

我们在AsyncService接口中添加一个方法:returnMessage(),并使用Future接口来进行包装,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 异步回调消息方法
 *
 * @return 字符串
 */
Future<String> returnMessage();

实现类中的代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
@Async
public Future<String> returnMessage() {
    System.out.println(Thread.currentThread().getName());
    String message = "Async Method Result";
    return new AsyncResult<>(message);
}

那么在Controller层,就可以获取到Future的实现类对象,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@GetMapping("/page1")
public String asyncPage1() {
    try {
        System.out.println(Thread.currentThread().getName());
        Future<String> result = asyncService.returnMessage();
        System.out.println(result.get());
    } catch (ExecutionException | InterruptedException e) {
        log.error("发生了异常:{}", e.getMessage());
    }
    return "async";
}

这里对异步进行了try...catch异常处理,也使用了Futureget方法获取了异步方法的返回值,但是这种获取返回值的方式会阻塞当前线程,也就是说调用了get方法之后,会等待异步线程执行完毕后才进行下一行代码的执行。

使用ListenableFuture接口

我们在AsyncService接口中添加一个方法:returnMsg(),并使用ListenableFuture接口来进行包装,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 异步回调消息方法
 *
 * @return 字符串
 */
ListenableFuture<String> returnMsg();

实现类中的代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
@Async
public ListenableFuture<String> returnMsg() {
    System.out.println(Thread.currentThread().getName());
    String message = "Async Method Result";
    return new AsyncResult<>(message);
}

那么在Controller层,就可以获取到ListenableFuture的实现类对象,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@GetMapping("/page2")
public String asyncPage2() {
    System.out.println(Thread.currentThread().getName());
    ListenableFuture<String> result = asyncService.returnMsg();
    result.addCallback(new SuccessCallback<String>() {
        @Override
        public void onSuccess(String result) {
            System.out.println("返回的结果是:" + result);
        }
    }, new FailureCallback() {
        @Override
        public void onFailure(Throwable ex) {
            log.error("发生了异常:{}", ex.getMessage());
        }
    });
    return "async";
}

从上面的代码中可以看出,在返回的结果中添加了两个回调,分别是异步处理成功的回调SuccessCallback接口的实现类对象和异步处理失败发生异常的回调FailureCallback接口的实现类对象。ListenableFuture接口是对Future接口的扩展,支持回调,有效的避免了线程阻塞问题,也就是说,它会监听Future接口的执行情况,一旦完成,就会调用onSuccess方法进行成功后的处理,一旦发生异常,就会调用onFailure方法进行异常处理。相比较而言,更加推荐使用ListenableFuture来进行有返回值的异步处理。对于Java1.8,其实更加推荐使用CompletableFuture或者guavaListenableFuture,感兴趣的同学可以进行深入研究,他们的处理异步能力会更加强悍。

总结

本文从配置线程池、开启异步线程机制到异步线程的异常处理,分步介绍了在Spring Boot中如何启用异步线程来提高代码的并发能力,虽然是基于Spring Boot,但是也同样适用其他的采用Spring的项目。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018/10/31 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Spring Boot(5) @Async异步线程池详解
在Spring4中,Spring中引入了一个新的注解@Async,这个注解让我们在使用Spring完成异步操作变得非常方便。
黄规速
2022/04/14
5.9K0
Spring Boot(5) @Async异步线程池详解
Spring Boot系列二 Spring @Async异步线程池用法总结
转载 自 https://blog.csdn.net/hry2015/article/details/67640534
allsmallpig
2021/02/25
1.2K0
Spring的异步线程处理
再编写一个Service的异步方法调用,这里是带返回值的,不带返回值的方法比较简单,这里就不举例了。
算法之名
2020/10/26
8650
Spring 异步线程池的使用 原
只需要创建一个 Java 配置类, 实现 AsyncConfigurer 接口, 实现 getAsyncExecutor 方法返回线程池. 在 java 配置文件类上加注解 @EnableAsync 开启异步可用, 然后就可以在 service 方法上使用注解 @Async 使用异步调用
北漂的我
2019/05/29
1.4K0
如何在 Spring 异步调用中传递上下文什么是异步调用?
异步调用是相对于同步调用而言的,同步调用是指程序按预定顺序一步步执行,每一步必须等到上一步执行完后才能执行,异步调用则无需等待上一步程序执行完即可执行。异步调用指,在程序在执行时,无需等待执行的返回值即可继续执行后面的代码。在我们的应用服务中,有很多业务逻辑的执行操作不需要同步返回(如发送邮件、冗余数据表等),只需要异步执行即可。
aoho求索
2019/08/06
2.5K0
如何在 Spring 异步调用中传递上下文什么是异步调用?
快速搞懂Spring中实现异步调用的方式有哪些?
一位3年工作经验的小伙伴被问到这样一道面试题,说Spring中实现异步调用的方式有哪些?
Tom弹架构
2022/12/19
1.8K0
快速搞懂Spring中实现异步调用的方式有哪些?
关于Spring中的@Async注解以及为什么不建议使用 - Java技术债务
Async 注解是 Java 8 中的一个注解,用于标识一个方法是异步执行的。当一个方法被标记为 Async 时,该方法将在一个新的线程中执行,并且可以立即返回一个 CompletableFuture 对象。使用 CompletableFuture 可以更轻松地管理异步计算的结果。下面是一个使用 Async 注解的示例代码:
Java技术债务
2024/06/21
5710
SpringBoot异步调用
除了异步请求,一般上我们用的比较多的应该是异步调用。通常在开发过程中,会遇到一个方法是和实际业务无关的,没有紧密性的。比如记录日志信息等业务。这个时候正常就是启一个新线程去做一些业务处理,让主线程异步的执行其他业务。
全栈程序员站长
2022/09/14
1.3K0
SpringBoot异步调用
【小家Spring】Spring异步处理@Async的使用以及原理、源码分析(@EnableAsync)
在开发过程中,我们会遇到很多使用线程池的业务场景,例如异步短信通知、异步记录操作日志。大多数使用线程池的场景,就是会将一些可以进行异步操作的业务放在线程池中去完成。
YourBatman
2019/09/03
7.4K1
【小家Spring】Spring异步处理@Async的使用以及原理、源码分析(@EnableAsync)
Spring异步核心@Async注解的前世今生
可以很明显的发现,它使用的是线程池SimpleAsyncTaskExecutor,这也是Spring默认给我们提供的线程池(其实它不是一个真正的线程池,后面会有讲述)。下面原理部分讲解后,你就能知道怎么让它使用我们自定义的线程池了。
大忽悠爱学习
2022/09/29
1.3K0
Spring异步核心@Async注解的前世今生
异步编程 - 08 Spring框架中的异步执行_TaskExecutor接口和@Async应用篇
在Spring Framework中分别使用TaskExecutor和TaskScheduler接口提供异步执行和任务调度的抽象。
小小工匠
2023/09/09
2.3K0
异步编程 - 08 Spring框架中的异步执行_TaskExecutor接口和@Async应用篇
SpringBoot学习笔记(十七:异步调用)
在实际开发中,有时候为了及时处理请求和进行响应,我们可能使用异步调用,同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行;异步调用指程序在顺序执行时,不等待异步调用的语句返回结果就执行后面的程序。异步调用的实现有很多,例如多线程、定时任务、消息队列等。
三分恶
2020/07/16
6780
【云+社区年度征文】复盘Spring中定时任务和异步线程池
项目中最近使用了多个定时任务处理业务需求,于是在实现业务逻辑过程中,产生了上图一些思考和疑问,现在利用空余时间进行一次复盘。
沁溪源
2020/12/03
6220
【云+社区年度征文】复盘Spring中定时任务和异步线程池
【Spring】Spring高级话题-多线程-TaskExecutor
在Spring中,通过任务执行器,也就是TaskExecutor来实现多线程和并发编程。
谙忆
2021/01/21
4550
【Spring】Spring高级话题-多线程-TaskExecutor
SpringBoot异步任务记录
简介 突发奇想,就想玩一下异步任务,记得之前项目有个场景需要使用异步执行,但是异步调用没有成功,后来采用了多线程去执行,今天就系统的学习下异步执行任务。记录一下 有时候在项目中,当访问其他人的接口较慢或者做耗时任务时,不想程序一直卡在耗时任务上,想程序能够并行执行, 我们可以使用多线程来并行的处理任务,也可以使用 spring 提供的异步处理方式 @Async 。 在异步处理的方法上添加注解 @Async ,就会启动一个新的线程去执行。 Spring 通过任务执行器 TaskExecutor ,来实现多线程
框架师
2021/08/05
7720
Spring 异步调用,一行代码实现!舒服,不接受任何反驳~
在日常开发中,我们的逻辑都是同步调用,顺序执行。在一些场景下,我们会希望异步调用,将和主线程关联度低的逻辑异步调用,以实现让主线程更快的执行完成,提升性能。例如说:记录用户访问日志到数据库,记录管理员操作日志到数据库中。
芋道源码
2020/05/25
6.1K0
java之异步线程Executor
说起异步,下意识就想到同步,这两个概念每个人都有着不同的理解,在这我们只是提下这关键词,具体的理解,可自行斟酌。
码农王同学
2019/10/30
1.5K0
SpringBoot中使用异步方法优化Service逻辑,提高接口响应速度
异步方法适用于逻辑与逻辑之间可以相互分割互不影响的业务中, 如生成验证码和发送验证码组成的业务, 其实无需等到真正发送成功验证码才对客户端进行响应, 可以让短信发送这一耗时操作转为异步执行, 解耦耗时操作和核心业务;
良月柒
2021/11/10
4750
不仅会用@Async,我把源码也梳理了一遍(上)
说起异步化,很多人会想起异步线程、消息队列等,消息队列不是文章的主题,今天我们来聊聊spring对异步化的支持@EnableAsync&@Async。
java思维导图
2019/09/24
8.5K0
不仅会用@Async,我把源码也梳理了一遍(上)
SpringBoot中Async异步方法和定时任务介绍
1.功能说明 Spring提供了Async注解来实现方法的异步调用。 即当调用Async标识的方法时,调用线程不会等待被调用方法执行完成即返回继续执行以下操作,而被调用的方法则会启动一个独立线程来执行此方法。 这种异步执行的方式通常用于处理接口中不需要返回给用户的数据处理。比如当注册的时候,只需要将用户信息返回用户,而关于信息的保存操作可以使用异步执行。 Spring提供了Scheduled注解来实现定时任务的功能。 在异步方法和定时任务功能中都是开发这自己定义需要执行的方法,然后交给Spring容器管理
代码改变世界-coding
2018/07/03
2.2K0
推荐阅读
相关推荐
Spring Boot(5) @Async异步线程池详解
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档