Servlet 3.x 概述
在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理。如果一个请求需要进行I0操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待lO操作完成,而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。
Servlet3.x 的新特性
- 异步处理支持:有了该特性,Servlet线程不再需要一直阻塞,直到业务处理完毕才能再输出响应,最后才结束该Servlet线程。在接收到请求之后,Servlet线程可以将耗时的操作委派给另一个线程来完成,自己在不生成响应的情况下返回至容器。针对业务处理较耗时的情况,这将大大减少服务器资源的占用,并且提高并发处理速度。
- 新增的注解支持:该版本新增了若干注解,用于简化Servlet、过滤器(Filter)和监听器(Listener)的声明,这使得 web.xml部署描述文件从该版本开始不再是必选的了。
例子
- 同步执行的方式
package com.study.servletdemo.servletDemo;
import com.study.servletdemo.dosomthing.DoSomThingRunning;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@WebServlet("/test")
public class Servlet4Sync1 extends HttpServlet {
private static ThreadPoolExecutor executor =
new ThreadPoolExecutor(100, 200, 5000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(200));
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//TODO 这里面写逻辑
System.out.println("当前处理请求的线程是:" + Thread.currentThread().getName());
new DoSomThingRunning().run(); //调用一个方法.
resp.getWriter().write("OK,get a response");
}
}
- 异步返回的方式
package com.study.servletdemo.servletDemo;
import com.study.servletdemo.dosomthing.DoSomThingRunning;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@WebServlet(value = "/test1", asyncSupported = true)
public class ServletSync2 extends HttpServlet {
private static ThreadPoolExecutor executor =
new ThreadPoolExecutor(100, 200, 5000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(200));
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
AsyncContext asyncContext = req.startAsync(); //实现异步机制
System.out.println("当前运行的Thread:" + Thread.currentThread().getName());
executor.execute(() -> {
new DoSomThingRunning().run();
try {
asyncContext.getResponse().getWriter().write("ok,is response");
} catch (IOException e) {
e.printStackTrace();
}
asyncContext.complete();
});
// 非线程池的方式
// asyncContext.start(()->{
// new DoSomThingRunning().run();
// try {
// asyncContext.getResponse().getWriter().write("ok,is response");
// } catch (IOException e) {
// e.printStackTrace();
// }
// asyncContext.complete();
// });
}
}
DeferredResult 异步处理
在SpringMVC中,基于Servlet3.x的异步特性,Spring也提供了一套异步处理的方案。基于DeferredResult来实现异步处理。
DeferredResult的主要流程:
-
客户端〈即前端)请求之后,在Controller层返回一个DeferredResult对象,将其保存在内在队列或者可以访问它的列表中。
-
其他请求进入之后,SpringMVC开始异步处理,DispatcherServlet与所有Filter的线程退出,response依然开放。
-
当正常的结果完成返回之后,通过application进行返回DeferredResult中的sets值。
-
注意:整体思路类似于callable中的返回值的方法。
DeferredResult处理流程
DeferredResult 的例子
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@RestController
public class DeferredResultController {
private LinkedBlockingQueue<DeferredResult> queue = new LinkedBlockingQueue<>();
ThreadPoolExecutor executor =
new ThreadPoolExecutor(1000, 1500, 5000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(200));
@RequestMapping("/get")
public DeferredResult<String> getResult() {
final DeferredResult<String> stringDeferredResult = new DeferredResult<>(3000L); //
queue.add(stringDeferredResult);
executor.submit(new Runnable() {
@Override
public void run() {
try {
Random random = new Random();
int time = random.nextInt(5000);
System.out.println("休眠时间:" + time);
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
//处理业务的代码
System.out.println("处理业务");
String rs = "返回值...";
stringDeferredResult.setResult(rs);
}
});
stringDeferredResult.onTimeout(new Runnable() { //超时之后执行...
@Override
public void run() {
//TODO --自己的业务
System.out.println("超时处理...");
stringDeferredResult.setResult("超时的返回结果");
}
});
stringDeferredResult.onCompletion(new Runnable() {
@Override
public void run() {
System.out.println("任务完成了..把队列中的deferredRueslt移除");
queue.remove();
}
});
return stringDeferredResult;
}
@Scheduled(fixedRate = 1000)
public void scheduleResult() {
for (int i = 0; i < queue.size(); i++) {
DeferredResult<String> stringDeferredResult = queue.poll();
stringDeferredResult.setResult("result:" + i);
}
}
}
总结
- 场景:
- 使用DeferredResult可以明显提高系统吞吐量。
- 在不使用异步处理的业务时,处理任务的线程一直都是主线程,也就是接收http请求的这根线程。一直会等到http请求返回或者异常之后,此线程才会释放。
- 使用了DeferredResult之后,就会由另外的线程去处理,从而释放主线程。
如果觉得有收获就点个赞吧,更多知识,请点击关注查看我的主页信息哦~
网友评论