SpringBoot中@Async异步的使用及异步与同步的区别

简介

在开发过程中,异步是提升系统并发能力的一个重要利器。而 spring 中的 @Async 异步注解,使我们能够非常方便地实现方法地异步调用。接下来主要结合以下几个问题来讲述 java 程序中的异步的使用:

  • 什么是同步
  • 什么是异步,以及异步的作用
  • 如何在 SpringBoot 中使用异步

1、什么是同步

同步调用,是遵循的顺序处理,一个一个调用

特征:在调用多个方法的时候,强调先后顺序,执行完一个方法再执行下一个方法,方法与方法之间不可调换先后顺序,不会并行执行

示例:

package com.example.demo.test1;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.concurrent.Future;

@Component
@Slf4j
public class SyncDemo {

    public void test1() throws Exception {
        log.info("test1开始执行");
        Long start = System.currentTimeMillis();
        Thread.sleep(1000);
        Long end = System.currentTimeMillis();
        log.info("test1执行结束");
        log.info("test1执行时长:{}毫秒", end-start);
    }

    public void test2() throws Exception {
        log.info("test2开始执行");
        Long start = System.currentTimeMillis();
        Thread.sleep(1500);
        Long end = System.currentTimeMillis();
        log.info("test2执行结束");
        log.info("test2执行时长:{}毫秒", end-start);
    }

    public void test3() throws Exception {
        log.info("test3开始执行");
        Long start = System.currentTimeMillis();
        Thread.sleep(2000);
        Long end = System.currentTimeMillis();
        log.info("test3执行结束");
        log.info("test3执行时长:{}毫秒", end-start);
    }

    public static void main(String[] args) throws Exception {
        SyncDemo syncDemo = new SyncDemo();
        Long start = System.currentTimeMillis();
        syncDemo.test1();
        syncDemo.test2();
        syncDemo.test3();
        Long end = System.currentTimeMillis();
        log.info("总执行时长:{}毫秒", end-start);
    }

}

执行结果:

netty随spring异步启动 java spring 异步_Async

总执行时长,是test1(),test2(),test3() 三个方法的近似总和

2、什么是异步

异步主要是调用方法后,不用等方法执行完毕就直接返回。异步方法会与其他方法并行执行,以此来节约时间,提高程序的并发能力。

示例:

为准确统计所有异步方法执行时间,现在将方法的返回对象,改为 Future,以此来可以判断方法是否执行完毕

示例代码如下:

IAsyncDemoService.java

package com.example.demo.test1;

import java.util.concurrent.Future;

public interface IAsyncDemoService {

    public Future<String> test1()  throws Exception ;

    public Future<String> test2()  throws Exception ;

    public Future<String> test3()  throws Exception ;

}

AsyncDemoServiceImpl.java

package com.example.demo.test1;

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

import java.util.concurrent.Future;

@Service
@Slf4j
public class AsyncDemoServiceImpl implements IAsyncDemoService {

    @Async
    @Override
    public Future<String> test1() throws Exception {
        log.info("test1开始执行");
        Long start = System.currentTimeMillis();
        Thread.sleep(1000);
        Long end = System.currentTimeMillis();
        log.info("test1执行结束");
        log.info("test1执行时长:{}毫秒", end-start);
        return new AsyncResult<>("test1完成");
    }

    @Async
    @Override
    public Future<String> test2() throws Exception {
        log.info("test2开始执行");
        Long start = System.currentTimeMillis();
        Thread.sleep(1500);
        Long end = System.currentTimeMillis();
        log.info("test2执行结束");
        log.info("test2执行时长:{}毫秒", end-start);
        return new AsyncResult<>("test2完成");
    }

    @Async
    @Override
    public Future<String> test3() throws Exception {
        log.info("test3开始执行");
        Long start = System.currentTimeMillis();
        Thread.sleep(2000);
        Long end = System.currentTimeMillis();
        log.info("test3执行结束");
        log.info("test3执行时长:{}毫秒", end-start);
        return new AsyncResult<>("test3完成");
    }
}

DemoApplicationTests.java

package com.example.demo;

import com.example.demo.test1.IAsyncDemoService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.concurrent.Future;

@SpringBootTest
@Slf4j
class DemoApplicationTests {

    @Autowired
    IAsyncDemoService asyncDemoService;

    @Test
    void contextLoads() throws Exception{
        Long start = System.currentTimeMillis();
        Future task1 = asyncDemoService.test1();
        Future task2 = asyncDemoService.test2();
        Future task3 = asyncDemoService.test3();

        while (true) {
            if (task1.isDone() && task2.isDone() && task3.isDone()) {
                break;
            }
        }
        Long end = System.currentTimeMillis();
        log.info("总执行时长:{}毫秒", end-start);
    }

}

执行结果如下:总执行时间与执行最长的 test3()方法的时间相近

netty随spring异步启动 java spring 异步_java_02

结论:异步方法是通过形式,调用后即返回,具体方法的执行,不影响其他方法的执行,多个方法并行的方式实现。

3、@Async 失效的情况

在使用 注解@Async 的时候,一定要注意规避以下情况,会造成 注解失效的情况

  • 注解@Async的方法不是public方法
  • 注解@Async的返回值只能为void或Future
  • 注解@Async方法使用static修饰也会失效
  • spring无法扫描到异步类,没加注解@Async或@EnableAsync注解
  • 调用方与被调用方不能在同一个类
  • 类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
  • 在Async方法上标注@Transactional是没用的.但在Async方法调用的方法上标注@Transcational是有效的

4、异步线程池的设置

使用 @Async 来实现异步,默认使用Spring创建ThreadPoolTaskExecutor,默认线程池的参数设置为:

  • 默认心线程数:8,
  • 最大线程数:Integet.MAX_VALUE,
  • 队列使用LinkedBlockingQueue,
  • 容量是:Integet.MAX_VALUE,
  • 空闲线程保留时间:60s,
  • 线程池拒绝策略:AbortPolicy。

问题:_
_并发情况下,会无限创建线程,导致系统资源耗尽,所以我们要手动设置线程池的配置,来避免无限创建线程的情况。

配置如下:
application.yaml

spring:
  task:
    execution:
      pool:
        max-size: 6
        core-size: 3
        keep-alive: 3s
        queue-capacity: 1000

自定义线程池配置类:

ExecutorConfig.java

@Configuration
@Data
public class ExecutorConfig{

    /**
     * 核心线程
     */
    private int corePoolSize;

    /**
     * 最大线程
     */
    private int maxPoolSize;

    /**
     * 队列容量
     */
    private int queueCapacity;

    /**
     * 保持时间
     */
    private int keepAliveSeconds;

    /**
     * 名称前缀
     */
    private String preFix;

    @Bean
    public Executor myExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        executor.setThreadNamePrefix(preFix);
        executor.setRejectedExecutionHandler( new ThreadPoolExecutor.AbortPolicy());
        executor.initialize();
        return executor;
    }
}

使用方式:

在使用 @Async 的时候,指定线程池为自定义线程池 myExecutor

例如:

@Async("myExecutor")
    public void test1() throws Exception {

    }