Github地址
相关系列文章:
Servlet Async Processing提供了一种异步请求处理的手段(见我的另一篇文章
Servlet 3.0 异步处理详解
),能够让你将Http thread从慢速处理中释放出来出来其他请求,提高系统的响应度。
但是光有Async Processing是不够的,因为整个请求-响应过程的速度快慢还牵涉到了客户端的网络情况,如果客户端网络情况糟糕,其上传和下载速度都很慢,那么同样也会长时间占用Http Thread使其不能被释放出来。
于是Servlet 3.1提供了Async IO机制,使得从Request中读、往Response里写变成异步动作。
Async Read
我们先来一段客户端上传速度慢的例子,
AsyncReadServlet.java
:
@WebServlet(value = "/async-read", asyncSupported = true)
public class AsyncReadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet thread: " + Thread.currentThread().getName());
AsyncContext asyncCtx = req.startAsync();
ServletInputStream is = req.getInputStream();
is.setReadListener(new ReadListener() {
private int totalReadBytes = 0;
@Override
public void onDataAvailable() {
System.out.println("ReadListener thread: " + Thread.currentThread().getName());
try {
byte buffer[] = new byte[1 * 1024];
int readBytes = 0;
while (is.isReady() && !is.isFinished()) {
int length = is.read(buffer);
if (length == -1 && is.isFinished()) {
asyncCtx.complete();
System.out.println("Read: " + readBytes + " bytes");
System.out.println("Total Read: " + totalReadBytes + " bytes");
return;
readBytes += length;
totalReadBytes += length;
System.out.println("Read: " + readBytes + " bytes");
} catch (IOException ex) {
ex.printStackTrace();
asyncCtx.complete();
@Override
public void onAllDataRead() {
try {
System.out.println("Total Read: " + totalReadBytes + " bytes");
asyncCtx.getResponse().getWriter().println("Finished");
} catch (IOException ex) {
ex.printStackTrace();
asyncCtx.complete();
@Override
public void onError(Throwable t) {
System.out.println(ExceptionUtils.getStackTrace(t));
asyncCtx.complete();
}
我们利用
curl
的
--limit-rate
选项来模拟慢速上传
curl -X POST -F "bigfile=@src/main/resources/bigfile" --limit-rate 5k http://localhost:8080/async-read
然后观察服务端的打印输出:
Servlet thread: http-nio-8080-exec-3
ReadListener thread: http-nio-8080-exec-3
Read: 16538 bytes
ReadListener thread: http-nio-8080-exec-4
Read: 16384 bytes
ReadListener thread: http-nio-8080-exec-5
Read: 16384 bytes
ReadListener thread: http-nio-8080-exec-7
Read: 16384 bytes
ReadListener thread: http-nio-8080-exec-6
Read: 16384 bytes
ReadListener thread: http-nio-8080-exec-8
Read: 16384 bytes
ReadListener thread: http-nio-8080-exec-9
Read: 16384 bytes
ReadListener thread: http-nio-8080-exec-10
Read: 2312 bytes
ReadListener thread: http-nio-8080-exec-1
Read: 48 bytes
Total Read: 117202 bytes
可以从输出看到除了doGet和第一次进入onDataAvailable是同一个Http thread之外,后面的read动作都发生在另外的Http thread里。
这是因为客户端的数据推送速度太慢了,容器先将Http thread收回,当容器发现可以读取到新数据的时候,再分配一个Http thread去读InputStream,如此循环直到全部读完为止。
注意:
HttpServletRequest.getInputStream()
和
getParameter*()
不能同时使用。
Async Write
再来一段客户端下载慢的例子,
AsyncWriteServlet.java
:
@WebServlet(value = "/async-write", asyncSupported = true)
public class AsyncWriteServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Servlet thread: " + Thread.currentThread().getName());
AsyncContext asyncCtx = req.startAsync();
ServletOutputStream os = resp.getOutputStream();
InputStream bigfileInputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("bigfile");
os.setWriteListener(new WriteListener() {
@Override
public void onWritePossible() throws IOException {
int loopCount = 0;
System.out.println("WriteListener thread: " + Thread.currentThread().getName());
while (os.isReady()) {
loopCount++;
System.out.println("Loop Count: " + loopCount);
byte[] bytes = readContent();
if (bytes != null) {
os.write(bytes);
} else {
closeInputStream();
asyncCtx.complete();
break;
@Override
public void onError(Throwable t) {
try {
os.print("Error happened");
os.print(ExceptionUtils.getStackTrace(t));
} catch (IOException e) {
e.printStackTrace();
} finally {
closeInputStream();
asyncCtx.complete();
private byte[] readContent() throws IOException {
byte[] bytes = new byte[1024];
int readLength = IOUtils.read(bigfileInputStream, bytes);
if (readLength <= 0) {
return null;
return bytes;
private void closeInputStream() {
IOUtils.closeQuietly(bigfileInputStream);
}
同样利用
curl
做慢速下载,
curl --limit-rate 5k http://localhost:8080/async-write
接下来看以下服务端打印输出:
Servlet thread: http-nio-8080-exec-1
WriteListener thread: http-nio-8080-exec-1
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-2
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-3
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-4
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-5
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-6
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-7
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-8
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-9
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-10
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-1
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-2
Write bytes: 8192
WriteListener thread: http-nio-8080-exec-3
Write bytes: 8192