Servlet3.0——异步请求处理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rubulai/article/details/80993844

在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理,当过来一个请求之后,会从tomcat的线程池中拿出一个线程去处理这个请求,处理完成之后再将该线程归还到线程池。但是线程池的数量是有限的,如果一个请求需要进行IO操作,比如访问数据库(或者调用第三方服务接口等),那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。即便是像Spring、Struts这样的高层框架也脱离不了这样的桎梏,因为他们都是建立在Servlet之上的。为了解决这样的问题,Servlet 3.0引入了异步处理,在Servlet 3.1中又引入了非阻塞IO来进一步增强异步处理的性能。

1、Servlet3.0中请求的异步处理:

@WebServlet(value="/async",asyncSupported=true)
public class HelloAsyncServlet extends HttpServlet {
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//1、支持异步处理asyncSupported=true
		//2、开启异步模式
		System.out.println("主线程开始。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
		AsyncContext startAsync = req.startAsync();
		
		//3、业务逻辑进行异步处理;开始异步处理
		startAsync.start(new Runnable() {
			@Override
			public void run() {
				try {
					System.out.println("副线程开始。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
					sayHello();
					startAsync.complete();
					//获取到异步上下文
					AsyncContext asyncContext = req.getAsyncContext();
					//4、获取响应
					ServletResponse response = asyncContext.getResponse();
					response.getWriter().write("hello async...");
					System.out.println("副线程结束。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
				} catch (Exception e) {
				}
			}
		});		
		System.out.println("主线程结束。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
	}

	public void sayHello() throws Exception{
		System.out.println(Thread.currentThread()+" processing...");
		Thread.sleep(3000);
	}
}

asyncSupported=true开启servlet的异步处理功能

AsyncContext startAsync = req.startAsync();获取异步上下文,也可以使用AsyncContext asyncContext = req.getAsyncContext();的方式获取异步上下文

startAsync.complete();异步请求完成通知

ServletResponse response = asyncContext.getResponse();获取响应,这样在异步请求中也可以给前端响应

这种情况下过来的请求先从tomcat的线程池中获取一个线程处理该请求,在该线程中又开启了一个异步线程去处理一些耗时的操作,这样就可以使tomcat的线程得以快速释放,就能够处理更多的并发请求了,但是这样情况下的处理异步请求的线程的管理也是由tomcat负责的,在SpringMVC中可以由SpringMVC负责管理。

2、SpringMVC的异步请求处理

①返回Callable<T>

@Controller
public class AsyncController {
	
	@ResponseBody
	@RequestMapping("/async01")
	public Callable<String> async01(){
		System.out.println("主线程开始..."+Thread.currentThread()+"==>"+System.currentTimeMillis());
		
		Callable<String> callable = new Callable<String>() {
			@Override
			public String call() throws Exception {
				System.out.println("副线程开始..."+Thread.currentThread()+"==>"+System.currentTimeMillis());
				Thread.sleep(2000);
				System.out.println("副线程开始..."+Thread.currentThread()+"==>"+System.currentTimeMillis());
				return "Callable<String> async01()";
			}
		};
		
		System.out.println("主线程结束..."+Thread.currentThread()+"==>"+System.currentTimeMillis());
		return callable;
	}

}

如果控制器返回Callable对象,则SpringMVC会将该请求进行异步处理——将Callable提交到TaskExecutor(由SpringMVC管理)中使用一个隔离的线程进行处理,同时DispatcherServlet和所有的Filter退出web容器的线程(可以继续处理其他的HTTP请求),但是response依然保持打开状态,用以异步的给浏览器响应,主线程处理完成,等待Callable执行完成。当Callable返回结果时,SpringMVC会将请求重新派发给容器(相当于重新发一个同样地请求给web容器),恢复之前的请求处理(此时已经拿到了处理结果——Callable的返回值,不会再执行目标方法),SpringMVC进入视图渲染等操作,

    异步的拦截器:
1)、原生API的AsyncListener

2)、SpringMVC:实现AsyncHandlerInterceptor

②返回DeferredResult<T>

controller:

@Controller
public class HelloController {

	@Autowired
	DeferredResultService deferredResultService;

	@ResponseBody
	@RequestMapping("/createOrder")
	public DeferredResult<Object> createOrder() {
		DeferredResult<Object> defer = new DeferredResult<Object>((long) 10000, "create fail...");
		deferredResultService.save(defer);
		return defer;
	}

	@ResponseBody
	@RequestMapping("/create")
	public String create() {
		String string = UUID.randomUUID().toString();
		DeferredResult<Object> deferredResult = deferredResultService.get();
		deferredResult.setResult(string);
		return "success" + string;
	}

}

DeferredResultService:

@Service
public class DeferredResultService {

	/**
	 * 队列
	 */
	private static Queue<DeferredResult<Object>> queue = new ConcurrentLinkedQueue<DeferredResult<Object>>();

	public void save(DeferredResult<Object> deferredResult) {
		queue.add(deferredResult);
	}

	public DeferredResult<Object> get() {
		return queue.poll();
	}
}

/createOrder请求中通过DeferredResultService的save方法往队列(先进先出)里面放了一个DeferredResult对象(此处为该对象设置了超时响应时间,也可以不设置),/create请求中从队列里面获取到/createOrder中放入的DeferredResult对象,并调用其setResult()方法,效果是在请求/createOrder响应超时之前请求/create会导致/createOrder也得到响应,若在请求/createOrder超时之后请求/create则/createOrder请求得到响应失败的响应,也就是说返回DeferredResult对象的请求的响应有两种情况:

    1)、在超时时间内未调用DeferredResult对象的setResult()方法导致的响应超时的异常响应;

    2)、在超时时间内的任何一个时点调用DeferredResult对象的setResult()方法,都会使返回DeferredResult对象的请求立即响应;

总结:返回DeferredResult对象的请求的响应是在调用该对象的setResult()后就会立即响应或者是超时后的异常响应,原理应该是在new DeferredResult对象的时候即会创建一个线程,在该对象的setResult()方法中监听该线程的执行情况并给出返回值。

猜你喜欢

转载自blog.csdn.net/rubulai/article/details/80993844