成都心情

  BlogJava :: 首页 ::  :: 联系 :: 聚合  :: 管理 ::
  98 随笔 :: 2 文章 :: 501 评论 :: 1 Trackbacks
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
posted on 2010-05-09 13:34 Rosen 阅读(5819) 评论(2)  编辑  收藏 所属分类: Java EE 服务器端

评论

# re: Servlet的异步转同步调用[未登录] 2010-05-09 23:49 Long
既然需要等待处理结果,为什么还要起一个线程来执行任务,直接在当前线程里执行不就行了??  回复  更多评论
  

# re: Servlet的异步转同步调用 2010-05-10 08:41 Rosen
@Long
这只是一个测试例子,在当前线程当然能实现一样的效果。  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: