Servlet(Server Applet)是Java Servlet的簡稱,稱為小服務(wù)程序或服務(wù)連接器,用Java編寫的服務(wù)器端程序,主要功能在于交互式地瀏覽和修改數(shù)據(jù),生成動(dòng)態(tài)Web內(nèi)容。
狹義的Servlet是指Java語言實(shí)現(xiàn)的一個(gè)接口,廣義的Servlet是指任何實(shí)現(xiàn)了這個(gè)Servlet接口的類,一般情況下,人們將Servlet理解為后者。Servlet運(yùn)行于支持Java的應(yīng)用服務(wù)器中。從原理上講,Servlet可以響應(yīng)任何類型的請求,但絕大多數(shù)情況下Servlet只用來擴(kuò)展基于HTTP協(xié)議的Web服務(wù)器。
最早支持Servlet標(biāo)準(zhǔn)的是JavaSoft的Java Web Server,此后,一些其它的基于Java的Web服務(wù)器開始支持標(biāo)準(zhǔn)的Servlet。
深入研究Servlet線程安全性問題
在探討java線程安全前,讓我們先簡要介紹一下Java語言。
任何語言,如C++,C#,Java,它們都有相通之處,特別是語法,但如果有人問你,Java語言的核心是什么?類庫?關(guān)鍵字?語法?似乎都不 是。Java語言的核心,也就是Sun始終不愿意開源的東西:Java虛擬機(jī)的實(shí)現(xiàn)(不過sun公開了其Java虛擬機(jī)規(guī)范),也就有了BEA的 JRockit,IBM的Jikes,Sun的Hotspot。
Java的核心有兩點(diǎn),Java類加載(Java Class Loader)和Java內(nèi)存管理,它們具體體現(xiàn)在Java類庫的以下幾個(gè)類:
java.lang.ClassLoader(java.lang.Class):我們調(diào)用的類,包括其接口和超類,import的類是怎么被Java虛擬機(jī)載入的?為什么static的字段在servlet容器里面可以一直生存下去(Spring容器中)?
java.lang.Thread(java.lang.ThreadLocal):垃圾回收是怎么進(jìn)行的(垃圾回收線程)?我們的程序是怎么退出的?
java.lang.refelect.Proxy(java.lang.refelect.Method):為什么Tomcat、 Tapestry、Webwork、Spring等容器和框架可以通過配置文件來調(diào)用我們寫的類?Servlet規(guī)范、JSF規(guī)范、EJB規(guī)范、JDBC 規(guī)范究竟是怎么回事?為什么它們幾乎都是一些接口,而不是具體類?
Servlet線程安全
在Java的server side開發(fā)過程中,線程安全(Thread Safe)是一個(gè)尤為突出的問題。因?yàn)槿萜鳎鏢ervlet、EJB等一般都是多線程運(yùn)行的。雖然在開發(fā)過程中,我們一般不考慮這些問題,但診斷問題 (Robust),程序優(yōu)化(Performance),我們必須深入它們。
什么是線程安全?
引用
Thread-safe describes a program portion or routine that can be called from multiple programming threads without unwanted interaction between the threads。
在Java里,線程安全一般體現(xiàn)在兩個(gè)方面:
1、多個(gè)thread對同一個(gè)java實(shí)例的訪問(read和modify)不會(huì)相互干擾,它主要體現(xiàn)在關(guān)鍵字synchronized。如 ArrayList和Vector,HashMap和Hashtable(后者每個(gè)方法前都有synchronized關(guān)鍵字)。如果你在 interator一個(gè)List對象時(shí),其它線程remove一個(gè)element,問題就出現(xiàn)了。
2、每個(gè)線程都有自己的字段,而不會(huì)在多個(gè)線程之間共享。它主要體現(xiàn)在java.lang.ThreadLocal類,而沒有Java關(guān)鍵字支持,如像static、transient那樣。
一個(gè)普遍的疑問,我們的Servlet中能夠像JavaBean那樣declare instance或static字段嗎?如果不可以?會(huì)引發(fā)什么問題?
答案是:不可以。我們下面以實(shí)例講解:
首先,我們寫一個(gè)普通的Servlet,里面有instance字段count:
web.xml 》》
1 《servlet》
2 《servlet-name》SimpleServlet《/servlet-name》
3 《servlet-class》servlet.SimpleServlet《/servlet-class》
4 《/servlet》
5 《servlet-mapping》
6 《servlet-name》SimpleServlet《/servlet-name》
7 《url-pattern》/SimpleServlet《/url-pattern》
8 《/servlet-mapping》
SimpleServlet 》》
1 public class SimpleServlet extends HttpServlet {
2 private int counter = 0;
3 @Override
4 protected void service(HttpServletRequest request, HttpServletResponse response)
5 throws ServletException, IOException {
6 response.getWriter().println(“《HTML》《BODY》”);
7 response.getWriter().println(this + “ ==》 ”);
8 response.getWriter().println(Thread.currentThread() + “: 《br》”);
9 for(int c=0;c《10;c++){
10 response.getWriter().println(“Counter = ” + counter + “《BR》”);
11 try {
12 Thread.sleep(1000);
13 counter++;
14 } catch (Exception e) {
15 e.printStackTrace();
16 }
17 }
18 response.getWriter().println(“《/BODY》《/HTML》”);
19 }
20 }
test.html 》》
1 《HTML》
2 《BODY》
3 《TABLE》
4 《TR》
5 《TD》《IFRAME src=“SimpleServlet” name=“servlet1” height=“200%”》 《/IFRAME》《/TD》
6 《/TR》
7 《/TABLE》
8 《/BODY》
9 《/HTML》
大家應(yīng)該發(fā)現(xiàn),test.html寫的和zwchen的博客原文中的寫的有點(diǎn)區(qū)別,本來也是按照zwchen的博客原文中的去測試的,但是相信很多人并沒有得出理想的結(jié)果,正如博客下面評論上5樓所說的:“沒有出現(xiàn)線程安全問題,數(shù)字的順序都是正確的”,我也是如此(我用的是Firefox瀏覽器)。后來換了IE瀏覽器進(jìn)行測試出現(xiàn)下面的問題,在頁面上只顯示出了第一個(gè)《tr》《/tr》里面的內(nèi)容,于是我的處理方法就是:test.html的內(nèi)容如上所示,打開3個(gè)IE瀏覽器,同時(shí)在瀏覽器中輸入:
a: http://localhost:8080/ServletTest/SimpleServlet
b: http://localhost:8080/ServletTest/SimpleServlet
c: http://localhost:8080/ServletTest/SimpleServlet
測試結(jié)果如下:
我們會(huì)發(fā)現(xiàn)三點(diǎn):
1、Servlet是一個(gè)單例對象(Singleton),因?yàn)槲覀兛吹蕉啻握埱蟮膖his指針?biāo)写蛴〕鰜淼膆ashCode值都相同。
2、servlet在不同的線程(線程池)中運(yùn)行,如http-8080-1,http-8080-2,http-8080-3 等輸出值可以明顯區(qū)分出不同的線程執(zhí)行了不同一段Servlet邏輯代碼。
3、count變量在不同的線程中共享,而且它的值被不同的線程修改,輸出時(shí)已經(jīng)不是順序輸出。也就是說,其他的線程會(huì)篡改當(dāng)前線程中實(shí)例變量的值,針對這些對象的訪問不是線程安全的。
上面的結(jié)果,違反了線程安全的兩個(gè)方面。
那么,我們怎樣保證按照我們期望的結(jié)果運(yùn)行呢?首先,我想保證產(chǎn)生的count都是順序執(zhí)行的。
我們將Servlet代碼重構(gòu)如下:
1 public class SimpleServlet extends HttpServlet {
2 private int counter = 0;
3 private String mutex = “”;
4 @Override
5 protected void service(HttpServletRequest request, HttpServletResponse response)
6 throws ServletException, IOException {
7 response.getWriter().println(“《HTML》《BODY》”);
8 response.getWriter().println(this + “ ==》 ”);
9 response.getWriter().println(Thread.currentThread() + “: 《br》”);
10 synchronized (mutex){
11 for(int c=0;c《10;c++){
12 response.getWriter().println(“Counter = ” + counter + “《BR》”);
13 try {
14 Thread.sleep(1000);
15 counter++;
16 } catch (Exception e) {
17 e.printStackTrace();
18 }
19 }
20 }
21 response.getWriter().println(“《/BODY》《/HTML》”);
22 }
23 }
這符合了我們的要求,輸出都是按順序的,這正式synchronized的含義。
評論
查看更多