創建了一個新的bean對象,而不是提取一個舊對象(例如,同一個用戶會話中更早的jsp頁面所創建的bean對象)。
下面是start.jsp頁面的代碼清單:
<% session.removeattribute("task"); %> <jsp:usebean id="task" scope="session" class="test.barbean.taskbean"/> <% task.setrunning(true); %> <% new thread(task).start(); %> <jsp:forward page="status.jsp"/> |
start.jsp創建并設置好taskbean對象之后,接著創建一個thread,并將bean對象作為一個runnable實例傳入。調用start()方法時新創建的線程將執行taskbean對象的run()方法。
現在有兩個線程在并發執行:執行jsp頁面的線程(稱之為“jsp線程”),由jsp頁面創建的線程(稱之為“任務線程”)。接下來,start.jsp利用調用status.jsp,status.jsp顯示出進度條以及任務的執行情況。注意status.jsp和start.jsp在同一個jsp線程中運行。
start.jsp在創建線程之前就把taskbean的running標記設置成了true,這樣,即使當jsp線程已開始執行status.jsp而任務線程的run()方法尚未啟動,也能夠確保用戶會得到“任務已開始運行”的狀態報告。
將running標記設置成true、啟動任務線程這兩行代碼可以移入taskbean構成一個新的方法,然后由jsp頁面調用這個新方法。一般而言,jsp頁面應當盡量少用java代碼,即我們應當盡可能地把java代碼放入java類。不過本例中我們不遵從這一規則,把new thread(task).start()直接放入start.jsp突出表明jsp線程創建并啟動了任務線程。
在jsp頁面中操作多線程必須謹慎,注意jsp線程和其它線程實際上是并發執行的,就象在桌面應用程序中,我們用一個線程來處理gui事件,另外再用一個或多個線程來處理后臺任務。
不過在jsp環境中,考慮到多個用戶同時請求某一個頁面的情況,同一個jsp頁面可能會在多個線程中同時運行;另外,有時同一個用戶可能會向同一個頁面發出多個請求,雖然這些請求來自同一個用戶,它們也會導致服務器同時運行一個jsp頁面的多個線程。
三、任務進度
status.jsp頁面利用一個html進度條向用戶顯示任務的執行情況。首先,status.jsp利用標記獲得start.jsp頁面創建的bean對象:
<jsp:usebean id="task" scope="session" class="test.barbean.taskbean"/> |
為了及時反映任務執行進度,status.jsp會自動刷新。javascript代碼settimeout("location=′status.jsp′", 1000)將每隔1000毫秒刷新頁面,重新請求status.jsp,不需要用戶干預。
<html> <head> <title>jsp進度條</title> <% if (task.isrunning()) { %> <script language="javascript"> settimeout("location=′status.jsp′", 1000); </script> <% } %> </head> <body> |
進度條實際上是一個html表格,包含10個單元,即每個單元代表任務總體的10%進度。
<h1 align="center">jsp進度條</h1> <h2 align="center"> |
結果:
<%= task.getresult() %><br> <% int percent = task.getpercent();%> <%= percent %>% </h2> <table width="60%" align="center" border=1 cellpadding=0 cellspacing=2> <tr> <% for (int i = 10; i <= percent; i += 10){ %> <td width="10%" bgcolor="#000080"> </td> <% } %> <% for (int i = 100; i > percent; i -= 10){ %> <td width="10%"> </td> <%} %> </tr> </table> |
任務執行情況分下面幾種狀態:正在執行,已完成,尚未開始,已停止:
<table width="100%" border=0 cellpadding=0 cellspacing=0> <tr> <td align="center"> <% if (task.isrunning()) { %> |
正在執行
<% } else { %> <% if (task.iscompleted()) { %> |
完成
<% } else if (!task.isstarted()){ %> |
尚未開始
已停止
<% } %> <% } %> </td> </tr> |
頁面底部提供了一個按鈕,用戶可以用它來停止或重新啟動任務:
<tr> <td align="center"> <br> <% if (task.isrunning()) { %> <form method="get" action="stop.jsp"> <input type="submit" value="停止"> </form> <% } else { %> <form method="get" action="start.jsp"> <input type="submit" value="開始"> </form> <% } %> </td> </tr> </table> </body></html> |
只要不停止任務,約10秒后瀏覽器將顯示出計算結果5050:
四、停止任務
stop.jsp頁面把running標記設置成false,從而停止當前的計算任務:
<jsp:usebean id="task" scope="session" class="test.barbean.taskbean"/> <% task.setrunning(false); %> <jsp:forward page="status.jsp"/> |
注意最早的java版本提供了thread.stop方法,但jdk從1.2版開始已經不贊成使用thread.stop方法,所以我們不能直接調用thread.stop()。
第一次運行本文程序的時候,你會看到任務的啟動有點延遲;同樣地,第一次點擊“停止”按鈕時也可以看到任務并沒有立即停止運行(特別是如果機器配置較低的話,延遲的感覺更加明顯),這些延遲都是由于編譯jsp頁面導致的。編譯好jsp頁面之后,應答速度就要快多了。
五、實際應用
進度條不僅使得用戶界面更加友好,而且對服務器的性能也有好處,因為進度條會不斷地告訴用戶當前的執行進度,用戶不會再頻繁地停止并重新啟動(刷新)當前的任務。另一方面,創建單獨的線程來執行后臺任務也會消耗不少資源,必要時可考慮通過一個線程池來實現thread對象的重用。另外,頻繁地刷新進度頁面也增加了網絡通信開銷,所以務必保持進度頁面簡潔短小。
在實際應用中,后臺執行的繁重任務可能不允許停止,或者它不能提供詳細的執行進度數據。例如,查找或更新關系數據庫時,sql命令執行期間不允許中途停止??不過如果用戶表示他想要停止或中止任務,程序可以在sql命令執行完畢后回退事務。
解析xml文檔的時候,我們沒有辦法獲知已解析內容精確的百分比。如果用dom解析xml文檔,直到解析完成后才得到整個文檔樹;如果用sax,雖然可以知道當前解析的內容,但通常不能確定還有多少內容需要解析。在這些場合,任務的執行進度只能靠估計得到。
估計一個任務需要多少執行時間通常是很困難的,因為它涉及到許多因素,即使用實際測試的辦法也無法得到可靠的結論,因為服務器的負載隨時都在變化之中。一種簡單的辦法是測量任務每次執行所需時間,然后根據最后幾次執行的平均時間估算。
如果要提高估計時間的精確度,應當考慮實現一種針對應用特點的算法,綜合考慮多種因素,例如要執行的sql語句類型、要解析的xml模式的復雜程度,等等。
結束語:本文例子顯示出用jsp、java、html和javascript構造進度條是相當容易的,真正困難的是如何將它用到實際應用之中,特別是獲得后臺任務的進度信息,但這個問題沒有通用的答案,每一種后臺執行的任務都有它自己的特點,必須按照具體情況具體分析。