SpringBoot-异步调用@Async

除了异步请求,一般上我们用的比较多的应该是异步调用。通常在开发过程中,会遇到一个方法是和实际业务无关的,没有紧密性的。比如记录日志信息等业务。这个时候正常就是启一个新线程去做一些业务处理,让主线程异步的执行其他业务。

异步调用?

通常开发过程中,一般上我们都是同步调用,即:程序按定义的顺序依次执行的过程,每一行代码执行过程必须等待上一行代码执行完毕后才执行。而异步调用指:程序在执行时,无需等待执行的返回值可继续执行后面的代码。显而易见,同步有依赖相关性,而异步没有,所以异步可并发执行,可提高执行效率,在相同的时间做更多的事情。

代码

SpringBoot启动类加上@EnableAsync注解

@SpringBootApplication
@EnableAsync
public class Application {

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

编写同步调用和异步调用方法

@Async
public void asyncEvent() throws InterruptedException{
    Thread.sleep(1000);
    log.info("async---{}", System.currentTimeMillis());
}

@Override
public void syncEvent() throws InterruptedException{
    Thread.sleep(1000);
    log.info("sync---{}", System.currentTimeMillis());
}

编写Controller调用

@Autowired
private AsyncService asyncService;

@GetMapping("async")
public Object doAsync() throws InterruptedException {
    long startTime = System.currentTimeMillis();
    log.info("方法开始执行---{}", startTime);

    asyncService.syncEvent();
    long syncTime = System.currentTimeMillis();
    log.info("同步方法执行用时--{}", syncTime - startTime);

    asyncService.asyncEvent();
    long asyncTime = System.currentTimeMillis();
    log.info("异步方法执行用时--{}", asyncTime - syncTime);

    log.info("方法执行结束--{}", System.currentTimeMillis());
    return "success";
}

调用打印日志为

2019-12-24 07:46:50.560  INFO 8904 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 方法开始执行---1577144810560
2019-12-24 07:46:51.560  INFO 8904 --- [nio-8080-exec-3] c.a.async.service.impl.AsyncServiceImpl  : sync---1577144811560
2019-12-24 07:46:51.560  INFO 8904 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 同步方法执行用时--1000
2019-12-24 07:46:51.560  INFO 8904 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 异步方法执行用时--0
2019-12-24 07:46:51.560  INFO 8904 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 方法执行结束--1577144811560
2019-12-24 07:46:52.561  INFO 8904 --- [         task-3] c.a.async.service.impl.AsyncServiceImpl  : async---1577144812561

注:在默认情况下,未设置TaskExecutor时,默认是使用SimpleAsyncTaskExecutor这个线程池,但此线程不是真正意义上的线程池,因为线程不重用,每次调用都会创建一个新的线程。可通过控制台日志输出可以看出,每次输出线程名都是递增的。
调用的异步方法,不能为同一个类的方法,简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。其他的注解如@Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。

如果在同一类的方法加上了异步调用注解

@GetMapping("asyncInOne")
public Object doAsyncInOne() throws InterruptedException {
    long startTime = System.currentTimeMillis();
    log.info("方法开始执行---{}", startTime);

    asyncService.syncEvent();
    long syncTime = System.currentTimeMillis();
    log.info("同步方法执行用时--{}", syncTime - startTime);

    asyncEventInOne();
    long asyncTime = System.currentTimeMillis();
    log.info("异步方法执行用时--{}", asyncTime - syncTime);

    log.info("方法执行结束--{}", System.currentTimeMillis());
    return "success";
}

@Async
public void asyncEventInOne() throws InterruptedException{
    Thread.sleep(1000);
    log.info("async-controller--{}", System.currentTimeMillis());

}

此时打印日志为:(可以看出并没有进行异步调用)

2019-12-24 07:51:43.550  INFO 11724 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 方法开始执行---1577145103550
2019-12-24 07:51:44.551  INFO 11724 --- [nio-8080-exec-3] c.a.async.service.impl.AsyncServiceImpl  : sync---1577145104551
2019-12-24 07:51:44.551  INFO 11724 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 同步方法执行用时--1001
2019-12-24 07:51:45.551  INFO 11724 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : async-controller--1577145105551
2019-12-24 07:51:45.551  INFO 11724 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 异步方法执行用时--1000
2019-12-24 07:51:45.551  INFO 11724 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 方法执行结束--1577145105551

自定义线程池

在默认情况下,系统使用的是默认的SimpleAsyncTaskExecutor进行线程创建。所以一般上我们会自定义线程池来进行线程的复用。

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

import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class ThreadConfig {

    @Bean(name = "asyncPoolTaskExecutor")
    public ThreadPoolTaskExecutor getAsyncTheadPoolExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(20);
        taskExecutor.setMaxPoolSize(200);
        taskExecutor.setQueueCapacity(25);
        taskExecutor.setKeepAliveSeconds(200);
        taskExecutor.setThreadNamePrefix("oKong-");
        //线程池对拒绝任务(无可用线程的处理策略,目前只支持AbortPolicy、CallerRunPolicy:默认为后者)
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();
        return taskExecutor;
    }
}

------------------------
corePoolSize:线程池维护线程的最少数量
keepAliveSeconds:允许的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
maxPoolSize:线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
queueCapacity:缓存队列
rejectedExecutionHandler:线程池对拒绝任务(无线程可用)的处理策略。这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。还有一个是AbortPolicy策略:处理程序遭到拒绝将抛出运行时RejectedExecutionException。
而在一些场景下,若需要在关闭线程池时等待当前调度任务完成后才开始关闭,可以通过简单的配置,进行优雅的停机策略配置。关键就是通过setWaitForTasksToCompleteOnShutdown(true)和setAwaitTerminationSeconds方法。
setWaitForTasksToCompleteOnShutdown:表明等待所有线程执行完,默认为false。
setAwaitTerminationSeconds:等待的时间,因为不能无限的等待下去。
------------------------

异步注解加上我们的线程池名称

扫描二维码关注公众号,回复: 8294629 查看本文章
@Async("asyncPoolTaskExecutor")
public void asyncEvent() throws InterruptedException{
    Thread.sleep(1000);
    log.info("async---{}", System.currentTimeMillis());
    log.info("async---线程名称{}", Thread.currentThread().getName());
}

此视再看打印的日志

2019-12-24 08:52:04.704  INFO 17824 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-12-24 08:52:04.704  INFO 17824 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-12-24 08:52:04.709  INFO 17824 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 5 ms
2019-12-24 08:52:04.727  INFO 17824 --- [nio-8080-exec-1] c.agan.async.controller.AsyncController  : 方法开始执行---1577148724727
2019-12-24 08:52:05.728  INFO 17824 --- [nio-8080-exec-1] c.a.async.service.impl.AsyncServiceImpl  : sync---1577148725728
2019-12-24 08:52:05.728  INFO 17824 --- [nio-8080-exec-1] c.agan.async.controller.AsyncController  : 同步方法执行用时--1001
2019-12-24 08:52:05.731  INFO 17824 --- [nio-8080-exec-1] c.agan.async.controller.AsyncController  : 异步方法执行用时--3
2019-12-24 08:52:05.731  INFO 17824 --- [nio-8080-exec-1] c.agan.async.controller.AsyncController  : 方法执行结束--1577148725731
2019-12-24 08:52:06.732  INFO 17824 --- [         task-1] c.a.async.service.impl.AsyncServiceImpl  : async---1577148726732
2019-12-24 08:52:06.732  INFO 17824 --- [         task-1] c.a.async.service.impl.AsyncServiceImpl  : async---线程名称task-1
2019-12-24 08:52:11.910  INFO 17824 --- [nio-8080-exec-2] c.agan.async.controller.AsyncController  : 方法开始执行---1577148731910
2019-12-24 08:52:12.910  INFO 17824 --- [nio-8080-exec-2] c.a.async.service.impl.AsyncServiceImpl  : sync---1577148732910
2019-12-24 08:52:12.910  INFO 17824 --- [nio-8080-exec-2] c.agan.async.controller.AsyncController  : 同步方法执行用时--1000
2019-12-24 08:52:12.910  INFO 17824 --- [nio-8080-exec-2] c.agan.async.controller.AsyncController  : 异步方法执行用时--0
2019-12-24 08:52:12.910  INFO 17824 --- [nio-8080-exec-2] c.agan.async.controller.AsyncController  : 方法执行结束--1577148732910
2019-12-24 08:52:13.910  INFO 17824 --- [         task-2] c.a.async.service.impl.AsyncServiceImpl  : async---1577148733910
2019-12-24 08:52:13.910  INFO 17824 --- [         task-2] c.a.async.service.impl.AsyncServiceImpl  : async---线程名称task-2
2019-12-24 08:52:18.368  INFO 17824 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 方法开始执行---1577148738368
2019-12-24 08:52:19.369  INFO 17824 --- [nio-8080-exec-3] c.a.async.service.impl.AsyncServiceImpl  : sync---1577148739369
2019-12-24 08:52:19.369  INFO 17824 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 同步方法执行用时--1001
2019-12-24 08:52:19.369  INFO 17824 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 异步方法执行用时--0
2019-12-24 08:52:19.369  INFO 17824 --- [nio-8080-exec-3] c.agan.async.controller.AsyncController  : 方法执行结束--1577148739369
2019-12-24 08:52:20.369  INFO 17824 --- [         task-3] c.a.async.service.impl.AsyncServiceImpl  : async---1577148740369
2019-12-24 08:52:20.369  INFO 17824 --- [         task-3] c.a.async.service.impl.AsyncServiceImpl  : async---线程名称task-3

异步回调与超时

对于一些业务场景下,需要异步回调的返回值时,就需要使用异步回调来完成了。主要就是通过Future进行异步回调。

@Async
public Future<String> asyncEventWithReturn() throws InterruptedException{
    Thread.sleep(1000);
    log.info("async---{}", System.currentTimeMillis());
    log.info("async---线程名称{}", Thread.currentThread().getName());
    return new AsyncResult<>("异步方法返回");
}

其中AsyncResult是Spring提供的一个Future接口的子类。
然后通过isDone方法,判断是否已经执行完毕。

@GetMapping("asyncWithReturn")
public Object doAsyncWithReturn() throws InterruptedException {
    long startTime = System.currentTimeMillis();
    log.info("方法开始执行---{}", startTime);

    asyncService.syncEvent();
    long syncTime = System.currentTimeMillis();
    log.info("同步方法执行用时--{}", syncTime - startTime);

    Future<String> stringFuture = asyncService.asyncEventWithReturn();
    while (true) {
        if (stringFuture.isDone()) {
            break;
        }
    }
    long asyncTime = System.currentTimeMillis();
    log.info("异步方法执行用时--{}", asyncTime - syncTime);

    log.info("方法执行结束--{}", System.currentTimeMillis());
    return "success";
}

调用结果为

2019-12-24 09:26:28.835  INFO 5492 --- [nio-8080-exec-2] c.agan.async.controller.AsyncController  : 方法开始执行---1577150788835
2019-12-24 09:26:29.837  INFO 5492 --- [nio-8080-exec-2] c.a.async.service.impl.AsyncServiceImpl  : sync---1577150789837
2019-12-24 09:26:29.837  INFO 5492 --- [nio-8080-exec-2] c.agan.async.controller.AsyncController  : 同步方法执行用时--1002
2019-12-24 09:26:30.840  INFO 5492 --- [        oKong-1] c.a.async.service.impl.AsyncServiceImpl  : async---1577150790840
2019-12-24 09:26:30.840  INFO 5492 --- [        oKong-1] c.a.async.service.impl.AsyncServiceImpl  : async---线程名称oKong-1
2019-12-24 09:26:30.841  INFO 5492 --- [nio-8080-exec-2] c.agan.async.controller.AsyncController  : 异步方法执行用时--1004
2019-12-24 09:26:30.841  INFO 5492 --- [nio-8080-exec-2] c.agan.async.controller.AsyncController  : 方法执行结束--1577150790841

对于一些需要异步回调的函数,不能无期限的等待下去,所以一般上需要设置超时时间,超时后可将线程释放,而不至于一直堵塞而占用资源。

try {
    stringFuture.get(100, TimeUnit.MILLISECONDS);
} catch (ExecutionException e) {
    e.printStackTrace();
} catch (TimeoutException e) {
    log.error("超时");
    e.printStackTrace();
}

猜你喜欢

转载自www.cnblogs.com/AganRun/p/12089695.html