AsyncResponse 是 JAX-RS 2.0 规范中关于”异步处理”的接口. 那么什么是异步处理呢?

常见 Web 容器采用多线程并发模型. 对于每个新来的会话, Web 容器分配(新建或从线程池中取出一个线程)来处理业务逻辑, 完成之后释放线程 (退出线程或放回线程池). 会话生命周期和线程生命周期(如果是线程池, 则逻辑上)一致. 这种方式是”同步处理”. 对于同步处理, 如果并发会话数很高而业务逻辑非常耗时, 则可能出现线程数暴涨或线程池耗尽的现象, 前者因系统上下文切换频繁引起的系统负载上升以致服务质量下降, 后者可能直接导致服务不可用. 解决这个问题的办法很多, “异步处理”是其中一种.

在”异步处理”方式下, 对于每个新来的会话, Web 容器仍然为其分配一个线程, 但是会话在这个线程中只执行简单(不耗时)的逻辑, 耗时逻辑交给另外的异步执行器, 然后立即马上释放线程. 因为每个线程处理会话的时间非常短, 所以可以用较少的线程处理较高的并发会话数. 和同步处理相比, 异步处理需要增加两项额外工作. 其一, 抽取耗时逻辑并提交到异步执行器. 异步执行器可以使用 juc 包下面的 ExecutorService, 抽取逻辑可以通过 Java 8 以后的 lambda 表达式实现. 其二, 在耗时处理逻辑结束以后提交结果给 Web 容器, 使其返回结果给服务请求者并关闭会话, 也就是并发编程中常见的异步回调. 异步回调通常做法是在异步执行单元 (比如上面说的 lambda 表达式)中调用一个特定接口的方法. JAX-RS 2.0 规定了这个接口的名字 AsyncResponse 和它的方法.

这篇文章 详细介绍了 AsyncResponse 和其他相关接口的使用. 文中给出了一个非常经典的 AsyncResponse 的例子, 其中会话线程的工作仅仅是提交耗时操作 (() -> service.veryExpensiveOperation()) 给异步执行器 (CompletableFuture.runAsync 默认使用 ForkJoinPool.commonPool()).

1
2
3
4
5
6
7
8
9
public class Resource {

        @GET
        public void asyncGet(@Suspended final AsyncResponse asyncResponse) {
            CompletableFuture
                .runAsync(() -> service.veryExpensiveOperation())
                .thenApply((result) -> asyncResponse.resume(result));
        }
    }

“异步处理”自然不限于前面提到的做法. JAX-RS 2.0 通过 AsyncResponse 接口规范了一种典型的模式, 具体实现仍然取决于各Web容器或框架. Jersey 提供了这个规范的参考实现, 但 Spring 却有自己的一套玩法. 将来 Java 原生支持协程以后还可能出现新的模式.