异步调用相对于同步调用而言,通常的方法都是程序按照顺序来执行的,程序的每一步都需要等到上一步执行完成之后才能继续往下执行;而异步调用则无需等待,它可以在不阻塞主线程的情况下执行高耗时方法
实现异步调用
1、在主类中添加@EnableAsync注解
@EnableAsync @SpringBootApplication public class WebMvcApplication { public static void main(String[] args){ SpringApplication app = new SpringApplication(WebMvcApplication.class); app.run(args); } }
2、创建一个AsyncTask类
在里面添加两个用@Async
注解的task
@Component @Slf4j public class AsyncTask { @Async public Future<String> doTask1() throws InterruptedException{ log.info("Task1 started."); long start = System.currentTimeMillis(); Thread.sleep(5000); long end = System.currentTimeMillis(); log.info("Task1 finished, time elapsed: {} ms.", end-start); return new AsyncResult<>("Task1 accomplished!"); } @Async public Future<String> doTask2() throws InterruptedException{ log.info("Task2 started."); long start = System.currentTimeMillis(); Thread.sleep(3000); long end = System.currentTimeMillis(); log.info("Task2 finished, time elapsed: {} ms.", end-start); return new AsyncResult<>("Task2 accomplished!"); } }
调用:
public class AsyncController { @Autowired private AsyncTask asyncTask; @RequestMapping(value = "/async",method = RequestMethod.GET) public String task() throws InterruptedException, ExecutionException { Long time = System.currentTimeMillis(); Future<String> task1 = asyncTask.doTask1(); Future<String> task2 = asyncTask.doTask2(); while(true) { if(task1.isDone() && task2.isDone()) { log.info("Task1 result: {}", task1.get()); log.info("Task2 result: {}", task2.get()); break; } Thread.sleep(1000); } log.info("耗时:{} ms",System.currentTimeMillis()-time); return "success"; } }
Future接口:
用于获取异步计算的结果,可通过get()获取结果、cancel()取消、isDone()判断是否完成等操作。
- V get(): 获取结果,若无结果会阻塞至异步计算完成
- V get(long timeOut, TimeUnit unit):获取结果,超时返回null
- boolean isDone():执行结束(完成/取消/异常)返回true
- boolean isCancelled():任务完成前被取消返回true
- boolean cancel(boolean mayInterruptRunning):取消任务,未开始或已完成返回false,参数表示是否中断执行中的线程。
Future.cancel(true)
适用于长时间处于运行的任务,并且能够处理interruption 。
执行结果:
2019-12-20 15:52:44.685 INFO 9240 --- [ task-2]: Task2 started. 2019-12-20 15:52:44.685 INFO 9240 --- [ task-1]: Task1 started. 2019-12-20 15:52:47.685 INFO 9240 --- [ task-2]: Task2 finished, time elapsed: 3000 ms. 2019-12-20 15:52:49.685 INFO 9240 --- [ task-1]: Task1 finished, time elapsed: 5000 ms. 2019-12-20 15:52:49.686 INFO 9240 --- [nio-7091-exec-1]: Task1 result: Task1 accomplished! 2019-12-20 15:52:49.686 INFO 9240 --- [nio-7091-exec-1]: Task2 result: Task2 accomplished! 2019-12-20 15:52:49.686 INFO 9240 --- [nio-7091-exec-1]: 耗时:5007 ms
自定义的Executor:
@Configuration @EnableAsync public class ExecutorConfig { /** Set the ThreadPoolExecutor's core pool size. */ private int corePoolSize = 10; /** Set the ThreadPoolExecutor's maximum pool size. */ private int maxPoolSize = 200; /** Set the capacity for the ThreadPoolExecutor's BlockingQueue. */ private int queueCapacity = 10; @Bean("taskExecutor") public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(corePoolSize); executor.setMaxPoolSize(maxPoolSize); executor.setQueueCapacity(queueCapacity); executor.setThreadNamePrefix("default-"); executor.initialize(); return executor; } @Bean public Executor myAsync() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(corePoolSize); executor.setMaxPoolSize(maxPoolSize); executor.setQueueCapacity(queueCapacity); executor.setKeepAliveSeconds(60); executor.setThreadNamePrefix("MyExecutor-"); // rejection-policy:当pool已经达到max size的时候,如何处理新任务 // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } }
- 核心线程数10:线程池创建时候初始化的线程数
- 最大线程数200:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
- 缓冲队列10:用来缓冲执行任务的队列
- 允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
- 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
- 线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
自定义Executor的类名,放进@Async注解中:
public class AsyncTask { @Async("myAsync") public Future<String> doTask1() throws InterruptedException{ } @Async public Future<String> doTask2() throws InterruptedException{ } }
没类加类名的@Async注解,会使用 @Bean(“taskExecutor”)的Executor。
需要注意的问题:
需要注意的问题一:异步方法的定义位置问题
最好将异步调用的方法单独放在一个@Component类中,或者说不要将异步调用方法写在@Controller中,否则将无法进行调用,因为SpringBoot使用@Transaction需要经过事务拦截器,只有通过了该事务拦截器的方法才能被加入Spring的事务管理器中,而在同一个类中的一个方法调用另一个方法只会经过一次事务拦截器,所以如果是后面的方法使用了事务注解将不会生效,在这里异步调用也是同样的道理
需要注意的问题二:异步方法的事务调用问题
在@Async注解的方法上再使用@Transaction注解是无效的,在@Async注解的方法中调用Service层的事务方法是有效的
需要注意的问题三:异步方法必须是实例的
因为静态方法不能被override重写,因为@Async异步方法的实现原理是通过注入一个代理类到Bean中,该代理类集成这个Bean并且需要重写这个异步方法,所以需要是实例方法