什么是异步调用

程序将启用异步调用的操作交由独立线程执行,跳过等待异步调用返回结果,继续往下执行。

如何启用异步调用

  1. 在 Spring 的启动类(或配置类)上添加  @EnableAsync  开启异步操作支持;
  2. 在需要执行异步的方法上添加  @Async  注解,标记其为异步方法;

如何处理异步方法的返回结果

  1. 在异步方法中使用 Future 封装返回值,表示其为异步结果,但实际返回时需要使用 AsyncResult  类型,Future 是其顶级接口;
  2. 在异步调用时,当主程序需要调用异步结果时,必须进行自我阻塞(通常使用循环等待异步操作),直到异步方法完成,否则异步结果会丢失;

测试代码

  1. 配置启动类,开启异步支持。
1
2
3
4
5
6
7
// 忽略其他注解
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
  1. 创建一个异步方法,该方法接收字符串与秒数,执行后会在睡眠指定时间之后返回字符串。
1
2
3
4
5
6
7
8
9
10
11
// AsyncService.class
@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;
}
  1. 创建一个测试方法,该方法调用了上步创建的异步方法,并指定其睡眠 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
// AsyncServiceTest.class
@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);
}
  1. 结果如预期一致,异步结果在等待 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

  1. 取消启动类上的  @EnableAsync  注解,对比执行结果。
1
2
3
4
5
6
// @EnableAsync
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. 测试时不使用循环等待异步结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// AsyncServiceTest.class
@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());
}
}

评论