异步调用其实就是使用多线程的方式执行另外一段程序,刚开始学习多线程的时候听到最多的就是实现 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、测试结果:
可以看到日志的先后顺序 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){ ... }
总结: