PS:写作本文仅仅为了了却一桩心愿,只是一种尝试,基本上无实际应用价值。
早在N年多前,给别人做一web应用,期间要使用jsp调客户提供的Socket客户端,去获取远端数据。由于该客户端是异步处理,所以jsp发出请求后到底何时能获得数据,是个问题。遂想了个办法,既jsp固定sleep一个足够长的时间,然后Socket客户端把数据写到某静态变量中,等jsp的sleep超时,jsp再去那个静态区去抓。这种做法倒是能用,只是时好时坏,特别是网络环境差的时候,容易得到null的数据。当然,最后改成了循环sleep,直到把数据刷出来为止。
问题是解决了,但冒出一个念头,想把访问异步环境的过程转换为同步,既对Servlet进行wait,然后待异步返回后再notify它。说干就干,Servlet代码如下,以Tomcat 5.5为例。
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Rec extends HttpServlet {
private static final long serialVersionUID = 1L;
public String var;
public Rec() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String tName = Thread.currentThread().getName();
GoProcess p = new GoProcess(this, tName);
p.setName("GoPro");
p.start();
synchronized(this){
try{
System.out.println("servlet "+tName+" 开始等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
response.getWriter().write(var);
}
}
Tomcat 5.5默认启动25个守护线程,来响应浏览器请求。所以,当每个请求来了之后,Tomcat都会找个可用线程来响应(根据测试,在多标签浏览器中,多次打开相同Servlet,Tomcat只会由同一线程响应),我们可以得到该线程的名字,例如“http-8080-Processor25”。来看上面的代码,比较简单,把Servlet实例传递给异步处理线程,然后处理线程启动,Servlet线程自己wait。
import java.util.Date;
public class GoProcess extends Thread {
private Rec _rec;
private String _tName;
public GoProcess(Rec rec, String tName){
_rec = rec;
_tName = tName;
}
public void run(){
try {
long lptime = Math.round(Math.random()*100000);
System.out.println("servlet "+_tName+" 进入GoPro线程,开始干活。需要"+lptime+"毫秒");
Thread.sleep(lptime);
System.out.println("处理结束呼唤servlet "+_tName);
synchronized(_rec){
_rec.notify();
_rec.var = new Date().toString()+" done!";
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
处理线程模拟不同的网络环境进行sleep,可以把sleep之上的代码看成进入异步调用,之后的代码看成异步返回。打开一个firefox和两个ie6窗口,调试结果如下。
servlet http-8080-Processor24 开始等待
servlet http-8080-Processor24 进入GoPro线程,开始干活。需要49277毫秒
servlet http-8080-Processor25 开始等待
servlet http-8080-Processor25 进入GoPro线程,开始干活。需要7610毫秒
servlet http-8080-Processor23 开始等待
servlet http-8080-Processor23 进入GoPro线程,开始干活。需要20599毫秒
处理结束呼唤servlet http-8080-Processor25
处理结束呼唤servlet http-8080-Processor23
处理结束呼唤servlet http-8080-Processor24
看起来是能实现异步调用转换为同步阻塞调用的方式。不过就像我在最前面说的,毫无意义!因为一旦网络环境很差,有可能会导致浏览器长时间处于空白加载状态,非常糟糕的用户体验(BTW:没有实验过nio的连接器,猜想这样阻塞访问会严重影响nio的性能)。所以,在真正遇到怎样的问题时还不如用ajax的方式处理,在页面上开辟一小块信息区显示状态,然后ajax轮询服务器端异步返回的结果,一旦返回就立刻体现到页面上。好了,欢迎拍砖。
请注意!引用、转贴本文应注明原作者:Rosen Jiang 以及出处:
http://www.blogjava.net/rosen