PS:寫作本文僅僅為了了卻一樁心愿,只是一種嘗試,基本上無實際應(yīng)用價值。
早在N年多前,給別人做一web應(yīng)用,期間要使用jsp調(diào)客戶提供的Socket客戶端,去獲取遠端數(shù)據(jù)。由于該客戶端是異步處理,所以jsp發(fā)出請求后到底何時能獲得數(shù)據(jù),是個問題。遂想了個辦法,既jsp固定sleep一個足夠長的時間,然后Socket客戶端把數(shù)據(jù)寫到某靜態(tài)變量中,等jsp的sleep超時,jsp再去那個靜態(tài)區(qū)去抓。這種做法倒是能用,只是時好時壞,特別是網(wǎng)絡(luò)環(huán)境差的時候,容易得到null的數(shù)據(jù)。當(dāng)然,最后改成了循環(huán)sleep,直到把數(shù)據(jù)刷出來為止。
問題是解決了,但冒出一個念頭,想把訪問異步環(huán)境的過程轉(zhuǎn)換為同步,既對Servlet進行wait,然后待異步返回后再notify它。說干就干,Servlet代碼如下,以Tomcat 5.5為例。
Tomcat 5.5默認啟動25個守護線程,來響應(yīng)瀏覽器請求。所以,當(dāng)每個請求來了之后,Tomcat都會找個可用線程來響應(yīng)(根據(jù)測試,在多標(biāo)簽瀏覽器中,多次打開相同Servlet,Tomcat只會由同一線程響應(yīng)),我們可以得到該線程的名字,例如“http-8080-Processor25”。來看上面的代碼,比較簡單,把Servlet實例傳遞給異步處理線程,然后處理線程啟動,Servlet線程自己wait。
處理線程模擬不同的網(wǎng)絡(luò)環(huán)境進行sleep,可以把sleep之上的代碼看成進入異步調(diào)用,之后的代碼看成異步返回。打開一個firefox和兩個ie6窗口,調(diào)試結(jié)果如下。
看起來是能實現(xiàn)異步調(diào)用轉(zhuǎn)換為同步阻塞調(diào)用的方式。不過就像我在最前面說的,毫無意義!因為一旦網(wǎng)絡(luò)環(huán)境很差,有可能會導(dǎo)致瀏覽器長時間處于空白加載狀態(tài),非常糟糕的用戶體驗(BTW:沒有實驗過nio的連接器,猜想這樣阻塞訪問會嚴重影響nio的性能)。所以,在真正遇到怎樣的問題時還不如用ajax的方式處理,在頁面上開辟一小塊信息區(qū)顯示狀態(tài),然后ajax輪詢服務(wù)器端異步返回的結(jié)果,一旦返回就立刻體現(xiàn)到頁面上。好了,歡迎拍磚。
請注意!引用、轉(zhuǎn)貼本文應(yīng)注明原作者:Rosen Jiang 以及出處: http://www.aygfsteel.com/rosen
早在N年多前,給別人做一web應(yīng)用,期間要使用jsp調(diào)客戶提供的Socket客戶端,去獲取遠端數(shù)據(jù)。由于該客戶端是異步處理,所以jsp發(fā)出請求后到底何時能獲得數(shù)據(jù),是個問題。遂想了個辦法,既jsp固定sleep一個足夠長的時間,然后Socket客戶端把數(shù)據(jù)寫到某靜態(tài)變量中,等jsp的sleep超時,jsp再去那個靜態(tài)區(qū)去抓。這種做法倒是能用,只是時好時壞,特別是網(wǎng)絡(luò)環(huán)境差的時候,容易得到null的數(shù)據(jù)。當(dāng)然,最后改成了循環(huán)sleep,直到把數(shù)據(jù)刷出來為止。
問題是解決了,但冒出一個念頭,想把訪問異步環(huán)境的過程轉(zhuǎn)換為同步,既對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);
}
}
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個守護線程,來響應(yīng)瀏覽器請求。所以,當(dāng)每個請求來了之后,Tomcat都會找個可用線程來響應(yīng)(根據(jù)測試,在多標(biāo)簽瀏覽器中,多次打開相同Servlet,Tomcat只會由同一線程響應(yīng)),我們可以得到該線程的名字,例如“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("處理結(jié)束呼喚servlet "+_tName);
synchronized(_rec){
_rec.notify();
_rec.var = new Date().toString()+" done!";
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
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("處理結(jié)束呼喚servlet "+_tName);
synchronized(_rec){
_rec.notify();
_rec.var = new Date().toString()+" done!";
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
處理線程模擬不同的網(wǎng)絡(luò)環(huán)境進行sleep,可以把sleep之上的代碼看成進入異步調(diào)用,之后的代碼看成異步返回。打開一個firefox和兩個ie6窗口,調(diào)試結(jié)果如下。
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毫秒
處理結(jié)束呼喚servlet http-8080-Processor25
處理結(jié)束呼喚servlet http-8080-Processor23
處理結(jié)束呼喚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毫秒
處理結(jié)束呼喚servlet http-8080-Processor25
處理結(jié)束呼喚servlet http-8080-Processor23
處理結(jié)束呼喚servlet http-8080-Processor24
看起來是能實現(xiàn)異步調(diào)用轉(zhuǎn)換為同步阻塞調(diào)用的方式。不過就像我在最前面說的,毫無意義!因為一旦網(wǎng)絡(luò)環(huán)境很差,有可能會導(dǎo)致瀏覽器長時間處于空白加載狀態(tài),非常糟糕的用戶體驗(BTW:沒有實驗過nio的連接器,猜想這樣阻塞訪問會嚴重影響nio的性能)。所以,在真正遇到怎樣的問題時還不如用ajax的方式處理,在頁面上開辟一小塊信息區(qū)顯示狀態(tài),然后ajax輪詢服務(wù)器端異步返回的結(jié)果,一旦返回就立刻體現(xiàn)到頁面上。好了,歡迎拍磚。
請注意!引用、轉(zhuǎn)貼本文應(yīng)注明原作者:Rosen Jiang 以及出處: http://www.aygfsteel.com/rosen