SpringBoot>18 - 使用 @Async 异步调用

简介:

异步调用其实就是使用多线程的方式执行另外一段程序,刚开始学习多线程的时候听到最多的就是实现 Runable 接口、继承 Thread 类。而 Springboot 中提供了实现异步调用的注解。

个人学习总结:
链接:【springboot、springcloud、docker 等,学习目录】

同步调用、异步调用、回调的区别:

1、同步调用:阻塞式调用,最常见,按照业务代码从上到下、从左到右一步一步执行,遇 到卡壳只能等待或者程序挂掉。
2、异步调用:非阻塞式调用,相对于同步调用,不必等上一段程序执行完毕即可执行。
3、回调:对异步调用而言,返回异步调用的结果。

实现异步调用:

1、@EnableAsync 开启异步调用支持

@MapperScan("com.coolron.*.dao")
@SpringBootApplication
@EnableAsync
public class SpringbootApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootApplication.class, args);
    }
}

2、定义异步任务

@Slf4j
@Component
public class AsyncTask {
    /*
     *  使用@Async注解标注这是一个异步函数
     */
    @Async
    public Future task01(long currentTime){
        log.info(currentTime + ": >>>>>>>>>>进入异步方法:task01() >>>>>>>>>>>>");
        try {
            // 休眠5秒
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info(currentTime + ": >>>>>>>>>>离开异步方法:task01()>>>>>>>>>>>>>");
        return new AsyncResult<>("task01回调");
    }
}

3、定义controller

/**
 * @Auther: xf
 * @Date: 2018/11/27 21:20
 * @Description:
 */
@Slf4j
@RequestMapping(value = "async")
@RestController
public class TaskController {

    @Autowired
    private AsyncTask asyncTask;

    @GetMapping(value = "job01")
    public ApiResult job01() throws ExecutionException, InterruptedException {
        long currentTime = System.currentTimeMillis();

        log.info(currentTime + ": >>>>>>>>>>开始执行任务:job01() >>>>>>>>>>>>");
        Future future = asyncTask.task01(currentTime);

        //String s = future.get();  // 获取执行结果
        boolean done = future.isDone();  // 任务是否完成
        boolean cancelled = future.isCancelled();  // 任务是否取消
        //TimeUnit.SECONDS.sleep(2);
        //boolean cancel = future.cancel(true);
        //boolean isCancelled = future.isCancelled();
        log.info(currentTime + ": >>>>>>>>>>结束执行任务:job01() >>>>>>>>>>>>" + done + " " + cancelled );
        return ApiResult.ok();
    }
}

4、Future 接口,查看源码

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

可以发现其中定义了5个方法:

1、boolean cancel(boolean mayInterruptIfRunning):取消任务 取消任务成功返回true,失败返回false。
     参数mayInterruptIfRunning表示是否允许取消正在执行的任务,
              设置true,表示可以取消正在执行的任务。如果任务已经完成,则此方法一定返回false;如果任务正在执行,则返回true,
              设置false,则返回false。
2、boolean isCancelled():任务是否被取消成功
3、boolean isDone():任务是否已经完成
4、V get():获取执行结果,会产生阻塞
5、V get(long timeout, TimeUnit unit):获取执行结果,指定获取结果的时间,没获取到结果返回null。

5、测试结果:
SpringBoot>18 - 使用 @Async 异步调用_第1张图片
可以看到日志的先后顺序 job01() >> job01() >> task01() >> task01(),即异步调用成功。

注意:future.get() 方法是一个阻塞式的,即需要等到有返回结果代码才会往下继续执行,我在此处就遇到这个错,导致日志的输出顺序与同步调用一样,其实异步调用成功,只是使用get()方法会阻塞。

自定义线程池:

线程也是一种宝贵的资源,一般我们会自定义线程池来进行线程的重用。

1、创建线程池配置类

/**
 * @Auther: xf
 * @Date: 2018/11/27 22:45
 * @Description: 自定义线程池
 */
@Configuration
public class TaskPoolConfig {
    @Bean("taskExecutor")
    public Executor taskExecutor() {
        // 创建线程池
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数,初始化线程数
        executor.setCorePoolSize(10);
        // 最大线程数
        executor.setMaxPoolSize(20);
        // 缓存队列
        executor.setQueueCapacity(200);
        // 空闲时间
        executor.setKeepAliveSeconds(60);
        // 线程前缀
        executor.setThreadNamePrefix("custom-exec-");
        // 拒绝任务的处理器
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }
}

2、使用自定义线程池(在 @Async 上指定线程池名称)

@Async("taskExecutor")
public Future task01(long currentTime){ ... }

3、测试:
SpringBoot>18 - 使用 @Async 异步调用_第2张图片

总结:

  1. 在springboot中使用异步,只需使用注解开启和使用即可。
  2. 注意异步回调接口Future中的几个方法,尤其是 get() 方法是一个阻塞式的。
  3. 在执行多个异步操作的时候可结合isDone()方法决定多个异步调用执行的顺序或者是否继续执行下一个调用。
  4. spring 默认对线程池只做简单的处理,每次使用线程都会重新创建,为了充分利用线程资源,需要自定义线程池。

个人微信公众号,谢谢支持!
SpringBoot>18 - 使用 @Async 异步调用_第3张图片

你可能感兴趣的:(SpringBoot,系列一,SpringBoot系列一)