Spring Boot使用@Async实现异步调用

Stella981
• 阅读 788

异步调用对应的是同步调用,同步调用可以理解为按照定义的顺序依次执行,有序性;异步调用在执行的时候不需要等待上一个指令调用结束就可以继续执行。

我们将在创建一个 Spring Boot 工程来说明。具体工程可以参考github代码 https://github.com/UniqueDong/springboot-study async模块

pom 依赖如下:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-logging</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- logback -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-access</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.2</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

启动类如下:

@SpringBootApplication
public class AsyncApplication {

    public static void main(String[] args) {
        SpringApplication.run(AsyncApplication.class, args);
    }

}

定义线程池

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 异步线程池
 */
@Configuration
@EnableAsync
public class AsyncExecutorConfig {

    /**
     * Set the ThreadPoolExecutor's core pool size.
     */
    private int corePoolSize = 8;
    /**
     * Set the ThreadPoolExecutor's maximum pool size.
     */
    private int maxPoolSize = 16;
    /**
     * Set the capacity for the ThreadPoolExecutor's BlockingQueue.
     */
    private int queueCapacity = 200;

    private String threadNamePrefix = "AsyncExecutor-";

    @Bean("taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix(threadNamePrefix);

        // rejection-policy:当pool已经达到max size的时候,如何处理新任务  
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行  
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

}  

代码中我们通过 ThreadPoolTaskExecutor 创建了一个线程池。参数含义如下所示:

  • corePoolSize:线程池创建的核心线程数
  • maxPoolSize:线程池最大线程池数量,当任务数超过corePoolSize以及缓冲队列也满了以后才会申请的线程数量。
  • setKeepAliveSeconds: 允许线程空闲时间60秒,当maxPoolSize的线程在空闲时间到达的时候销毁。
  • ThreadNamePrefix:线程的前缀任务名字。
  • RejectedExecutionHandler:当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务

使用实战

@Slf4j
@Service
public class OrderService {
    public static Random random = new Random();


    @Autowired
    private AsyncTask asyncTask;

    public void doShop() {
        try {
            createOrder();
            // 调用有结果返回的异步任务
            Future<String> pay = asyncTask.pay();
            if (pay.isDone()) {
                try {
                    String result = pay.get();
                    log.info("异步任务返回结果{}", result);
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
                asyncTask.vip();
                asyncTask.sendSms();
            }
            otherJob();
        } catch (InterruptedException e) {
            log.error("异常", e);
        }
    }

    public void createOrder() {
        log.info("开始做任务1:下单成功");
    }

    /**
     * 错误使用,不会异步执行:调用方与被调方不能在同一个类。主要是使用了动态代理,同一个类的时候直接调用,不是通过生成的动态代理类调用
     */
    @Async("taskExecutor")
    public void otherJob() {
        log.info("开始做任务4:物流");
        long start = System.currentTimeMillis();
        try {
            Thread.sleep(random.nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        log.info("完成任务4,耗时:" + (end - start) + "毫秒");
    }

}

异步任务服务类

mport lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;

import java.util.Random;
import java.util.concurrent.Future;

@Component
@Slf4j
public class AsyncTask {
    public static Random random = new Random();


    @Async("taskExecutor")
    public void sendSms() throws InterruptedException {
        log.info("开始做任务2:发送短信");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("完成任务1,耗时:" + (end - start) + "毫秒");
    }

    @Async("taskExecutor")
    public Future<String> pay() throws InterruptedException {
        log.info("开始做异步返回结果任务2:支付");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("完成任务2,耗时:" + (end - start) + "毫秒");
        return new AsyncResult<>("会员服务完成");
    }

    /**
     * 返回结果的异步调用
     * @throws InterruptedException
     */
    @Async("taskExecutor")
    public void vip() throws InterruptedException {
        log.info("开始做任务5:会员");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("开始做异步返回结果任务5,耗时:" + (end - start) + "毫秒");
    }
}

单元测试

@RunWith(SpringRunner.class)
@SpringBootTest(classes = AsyncApplication.class)
public class AsyncApplicationTests {

    @Autowired
    private OrderService orderService;

    @Test
    public void testAsync() {
        orderService.doShop();
        try {
            Thread.currentThread().join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

结果展示

2019-05-16 20:25:06.577 [INFO ] [main] - zero.springboot.study.async.service.OrderService-52 开始做任务1:下单成功 
2019-05-16 20:25:06.586 [INFO ] [main] - zero.springboot.study.async.service.OrderService-60 开始做任务4:物流 
2019-05-16 20:25:06.599 [INFO ] [AsyncExecutor-1] - zero.springboot.study.async.service.AsyncTask-38 开始做异步返回结果任务2:支付 
2019-05-16 20:25:13.382 [INFO ] [AsyncExecutor-1] - zero.springboot.study.async.service.AsyncTask-42 完成任务2,耗时:6783毫秒 
2019-05-16 20:25:14.771 [INFO ] [main] - zero.springboot.study.async.service.OrderService-68 完成任务4,耗时:8184毫秒 

可以看到有的线程的名字就是我们线程池定义的前缀,说明使用了线程池异步执行。其中我们示范了一个错误的使用案例 **otherJob()**,并没有异步执行。

原因:

spring 在扫描bean的时候会扫描方法上是否包含@Async注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用时增加异步作用。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个 bean 也就是 this. method,所以就没有增加异步作用,我们看到的现象就是@Async注解无效。

关注公众号获取最新文章一起进步Spring Boot使用@Async实现异步调用

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
Java BIO
同步与异步,阻塞与非阻塞同步:当前线程发起了一个调用或请求,然后当前线程需要等待该调用结束返回结果才能继续往下进行其他操作。异步:当前线程发起了一个调用或请求,然后当前线程不需等待调用的执行结果就可以继续往下执行(请求交由另一个线程去执行),之后可以通过被调用者的状态改变或者被调用者主动发出通知来获得执行结果
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Stella981 Stella981
3年前
Spring 异步调用,多线程,一行代码实现
Spring异步调用,多线程概述快速入门异步回调异步异常处理自定义执行器1、概述在日常开发中,我们的逻辑都是同步调用,顺序
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这