zoukankan      html  css  js  c++  java
  • 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:等待的时间,因为不能无限的等待下去。
    ------------------------
    

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

    @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();
    }
    
  • 相关阅读:
    光脚丫学LINQ(025):如何验证DBML和外部映射文件
    使用LINQ to SQL将数据从一个数据库复制到另一个数据库
    用VS2010 C#写DLL文件并且调用(原创)
    linux初识
    Run Only One Copy Of Application
    SQL Server 2008开启远程连接
    用Visual C#做DLL文件
    SQL Server代理服务无法启动的处理方法(转载)
    QTP连接Oracle
    What's AJAX?
  • 原文地址:https://www.cnblogs.com/AganRun/p/12089695.html
Copyright © 2011-2022 走看看