什么是异步调用
程序将启用异步调用的操作交由独立线程执行,跳过等待异步调用返回结果,继续往下执行。
如何启用异步调用
- 在 Spring 的启动类(或配置类)上添加
@EnableAsync
开启异步操作支持;
- 在需要执行异步的方法上添加
@Async
注解,标记其为异步方法;
如何处理异步方法的返回结果
- 在异步方法中使用
Future
封装返回值,表示其为异步结果,但实际返回时需要使用 AsyncResult
类型,Future
是其顶级接口;
- 在异步调用时,当主程序需要调用异步结果时,必须进行自我阻塞(通常使用循环等待异步操作),直到异步方法完成,否则异步结果会丢失;
测试代码
- 配置启动类,开启异步支持。
1 2 3 4 5 6 7
| @EnableAsync public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
|
- 创建一个异步方法,该方法接收字符串与秒数,执行后会在睡眠指定时间之后返回字符串。
1 2 3 4 5 6 7 8 9 10 11
| @Async public Future<String> stringFuture(String str, long time) { try { Thread.sleep(time * 1000); return new AsyncResult<>(str); } catch (InterruptedException e) { e.printStackTrace(); } return null; }
|
- 创建一个测试方法,该方法调用了上步创建的异步方法,并指定其睡眠 15s 后返回 “hello future” 字符串,且会在等待异步结果时每秒打印一次等待时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Test public void futureString() { long begin = System.currentTimeMillis(); Future<String> future = asyncService.stringFuture("hello future", 15); while (true) { if (future.isDone()) { try { log.info("future result: {}", future.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } break; } try { log.info("已用时:{}ms", System.currentTimeMillis() - begin); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } log.info("futureString 共耗时:{}ms", System.currentTimeMillis() - begin); }
|
- 结果如预期一致,异步结果在等待 15s 后成功打印字符串,并每隔一秒打印等待时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| c.example.test.service.AsyncServiceTest : 已用时:8ms c.example.test.service.AsyncServiceTest : 已用时:1010ms c.example.test.service.AsyncServiceTest : 已用时:2010ms c.example.test.service.AsyncServiceTest : 已用时:3011ms c.example.test.service.AsyncServiceTest : 已用时:4011ms c.example.test.service.AsyncServiceTest : 已用时:5012ms c.example.test.service.AsyncServiceTest : 已用时:6013ms c.example.test.service.AsyncServiceTest : 已用时:7013ms c.example.test.service.AsyncServiceTest : 已用时:8013ms c.example.test.service.AsyncServiceTest : 已用时:9014ms c.example.test.service.AsyncServiceTest : 已用时:10014ms c.example.test.service.AsyncServiceTest : 已用时:11015ms c.example.test.service.AsyncServiceTest : 已用时:12015ms c.example.test.service.AsyncServiceTest : 已用时:13015ms c.example.test.service.AsyncServiceTest : 已用时:14016ms c.example.test.service.AsyncServiceTest : future result: hello future c.example.test.service.AsyncServiceTest : futureString 共耗时:15017ms
|
- 取消启动类上的
@EnableAsync
注解,对比执行结果。
1 2 3 4 5 6
| public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
|
发现主程序中并未在每秒间隔时间中打印等待时间,即在执行循环时,已经等待 15s,证明没有启用异步调用的程序被阻塞了。
1 2
| c.example.test.service.AsyncServiceTest : future result: hello future c.example.test.service.AsyncServiceTest : futureString 共耗时:15003ms
|
- 测试时不使用循环等待异步结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Test public void futureStringWithoutLoop() { log.info("无循环接收 future 结果测试"); long begin = System.currentTimeMillis(); Future<String> future = asyncService.stringFuture("hello future", 15); if (future.isDone()) { try { log.info("future result: {}", future.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } log.info("futureString 共耗时:{}ms", System.currentTimeMillis() - begin); }
|
主程序执行时直接跳过异步方法,且未能接收到异步结果,证明异步方法尽管可以使得程序并行操作,但是在最后接收结果时仍需使用循环进行自我阻塞等待异步结果,执行总时长应为 Max(主程序耗时,异步方法耗时)
。
1 2
| c.example.test.service.AsyncServiceTest : 无循环接收 future 结果测试 c.example.test.service.AsyncServiceTest : futureString 共耗时:41ms
|
注意事项
- 此处使用的
Future
接口接收异步结果,该是异步阻塞接口;
- 建议不使用无返回值异步方法,否则内部产生异常不会被感知;
@Async
可以通过注解属性指定线程池,默认使用 SimpleAsyncTaskExecutor
;
专有配置类
通过实现 AsyncConfigurer
接口可以指定 @Async
的默认线程池,即无需通过注解属性显示指定。
此处通过直接注入自定义线程池进行配置 @Async
,亦可在该配置类中直接创建线程池,即 new ThreadPoolTaskExecutor()
,并载入配置信息,可参考《Spring 自定义线程池》。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Setter @Slf4j @Component public class AsyncTaskExecutorConfig implements AsyncConfigurer {
@Resource(name = "taskExecutorPool") private ThreadPoolTaskExecutor executor;
@Override public Executor getAsyncExecutor() { return executor; }
@Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return (throwable, method, objects) -> log.error("异常信息: {}, 异常方法: {}", throwable.getMessage(), method.getName()); } }
|