Servlet如何處理多個(gè)請(qǐng)求訪問?
Servlet容器默認(rèn)是采用單實(shí)例多線程的方式處理多個(gè)請(qǐng)求的:
1.當(dāng)web服務(wù)器啟動(dòng)的時(shí)候(或客戶端發(fā)送請(qǐng)求到服務(wù)器時(shí)),Servlet就被加載并實(shí)例化(只存在一個(gè)Servlet實(shí)例);
2.容器初始化化Servlet主要就是讀取配置文件(例如tomcat,可以通過servlet.xml的<Connector>設(shè)置線程池中線程數(shù)目,初始化線程池通過web.xml,初始化每個(gè)參數(shù)值等等。
3.當(dāng)請(qǐng)求到達(dá)時(shí),Servlet容器通過調(diào)度線程(Dispatchaer Thread) 調(diào)度它管理下線程池中等待執(zhí)行的線程(Worker Thread)給請(qǐng)求者;
4.線程執(zhí)行Servlet的service方法;
5.請(qǐng)求結(jié)束,放回線程池,等待被調(diào)用;
(注意:避免使用實(shí)例變量(成員變量),因?yàn)槿绻嬖诔蓡T變量,可能發(fā)生多線程同時(shí)訪問該資源時(shí),都來操作它,照成數(shù)據(jù)的不一致,因此產(chǎn)生線程安全問題)
從上面可以看出:
第一:Servlet單實(shí)例,減少了產(chǎn)生servlet的開銷;
第二:通過線程池來響應(yīng)多個(gè)請(qǐng)求,提高了請(qǐng)求的響應(yīng)時(shí)間;
第三:Servlet容器并不關(guān)心到達(dá)的Servlet請(qǐng)求訪問的是否是同一個(gè)Servlet還是另一個(gè)Servlet,直接分配給它一個(gè)新的線程;如果是同一個(gè)Servlet的多個(gè)請(qǐng)求,那么Servlet的service方法將在多線程中并發(fā)的執(zhí)行;
第四:每一個(gè)請(qǐng)求由ServletRequest對(duì)象來接受請(qǐng)求,由ServletResponse對(duì)象來響應(yīng)該請(qǐng)求;Servlet/JSP技術(shù)和ASP、PHP等相比,由于其多線程運(yùn)行而具有很高的執(zhí)行效率。由于Servlet/JSP默認(rèn)是以多線程模式執(zhí)行的,所以,在編寫代碼時(shí)需要非常細(xì)致地考慮多線程的安全性問題。 JSP的中存在的多線程問題:
當(dāng)客戶端第一次請(qǐng)求某一個(gè)JSP文件時(shí),服務(wù)端把該JSP編譯成一個(gè)CLASS文件,并創(chuàng)建一個(gè)該類的實(shí)例,然后創(chuàng)建一個(gè)線程處理CLIENT端的請(qǐng)求。如果有多個(gè)客戶端同時(shí)請(qǐng)求該JSP文件,則服務(wù)端會(huì)創(chuàng)建多個(gè)線程。每個(gè)客戶端請(qǐng)求對(duì)應(yīng)一個(gè)線程。以多線程方式執(zhí)行可大大降低對(duì)系統(tǒng)的資源需求,提高系統(tǒng)的并發(fā)量及響應(yīng)時(shí)間. 對(duì)JSP中可能用的的變量說明如下:
實(shí)例變量: 實(shí)例變量是在堆中分配的,并被屬于該實(shí)例的所有線程共享,所以不是線程安全的.
JSP系統(tǒng)提供的8個(gè)類變量
JSP中用到的OUT,REQUEST,RESPONSE,SESSION,CONFIG,PAGE,PAGECONXT是線程安全的(因?yàn)槊總€(gè)線程對(duì)應(yīng)的request,respone對(duì)象都是不一樣的,不存在共享問題), APPLICATION在整個(gè)系統(tǒng)內(nèi)被使用,所以不是線程安全的. 局部變量: 局部變量在堆棧中分配,因?yàn)槊總€(gè)線程都有它自己的堆棧空間,所以是線程安全的.
靜態(tài)類: 靜態(tài)類不用被實(shí)例化,就可直接使用,也不是線程安全的. 外部資源: 在程序中可能會(huì)有多個(gè)線程或進(jìn)程同時(shí)操作同一個(gè)資源(如:多個(gè)線程或進(jìn)程同時(shí)對(duì)一個(gè)文件進(jìn)行寫操作).此時(shí)也要注意同步問題. 使它以單線程方式執(zhí)行,這時(shí),仍然只有一個(gè)實(shí)例,所有客戶端的請(qǐng)求以串行方式執(zhí)行。這樣會(huì)降低系統(tǒng)的性能 問題
問題一. 說明其Servlet容器如何采用單實(shí)例多線程的方式來處理請(qǐng)求
問題二. 如何在開發(fā)中保證servlet是單實(shí)例多線程的方式來工作(也就是說如何開發(fā)線程安全的servelt)。 一. Servlet容器如何同時(shí)來處理多個(gè)請(qǐng)求 Java的內(nèi)存模型JMM(Java Memory Model)
JMM主要是為了規(guī)定了線程和內(nèi)存之間的一些關(guān)系。根據(jù)JMM的設(shè)計(jì),系統(tǒng)存在一個(gè)主內(nèi)存(Main Memory),Java中所有實(shí)例變量都儲(chǔ)存在主存中,對(duì)于所有線程都是共享的。每條線程都有自己的工作內(nèi)存(Working Memory),工作內(nèi)存由緩存和堆棧兩部分組成,緩存中保存的是主存中變量的拷貝,緩存可能并不總和主存同步,也就是緩存中變量的修改可能沒有立刻寫到主存中;堆棧中保存的是線程的局部變量,線程之間無法相互直接訪問堆棧中的變量。根據(jù)JMM,我們可以將論文中所討論的Servlet實(shí)例的內(nèi)存模型抽象為圖所示的模型。 工作者線程Work Thread:執(zhí)行代碼的一組線程。
調(diào)度線程Dispatcher Thread:每個(gè)線程都具有分配給它的線程優(yōu)先級(jí),線程是根據(jù)優(yōu)先級(jí)調(diào)度執(zhí)行的。 Servlet采用多線程來處理多個(gè)請(qǐng)求同時(shí)訪問。servlet依賴于一個(gè)線程池來服務(wù)請(qǐng)求。線程池實(shí)際上是一系列的工作者線程集合。Servlet使用一個(gè)調(diào)度線程來管理工作者線程。 當(dāng)容器收到一個(gè)Servlet請(qǐng)求,調(diào)度線程從線程池中選出一個(gè)工作者線程,將請(qǐng)求傳遞給該工作者線程,然后由該線程來執(zhí)行Servlet的service方法。當(dāng)這個(gè)線程正在執(zhí)行的時(shí)候,容器收到另外一個(gè)請(qǐng)求,調(diào)度線程同樣從線程池中選出另一個(gè)工作者線程來服務(wù)新的請(qǐng)求,容器并不關(guān)心這個(gè)請(qǐng)求是否訪問的是同一個(gè)Servlet.當(dāng)容器同時(shí)收到對(duì)同一個(gè)Servlet的多個(gè)請(qǐng)求的時(shí)候,那么這個(gè)Servlet的service()方法將在多線程中并發(fā)執(zhí)行。
Servlet容器默認(rèn)采用單實(shí)例多線程的方式來處理請(qǐng)求,這樣減少產(chǎn)生Servlet實(shí)例的開銷,提升了對(duì)請(qǐng)求的響應(yīng)時(shí)間,對(duì)于Tomcat可以在server.xml中通過<Connector>元素設(shè)置線程池中線程的數(shù)目。 就實(shí)現(xiàn)來說:
調(diào)度者線程類所擔(dān)負(fù)的責(zé)任如其名字,該類的責(zé)任是調(diào)度線程,只需要利用自己的屬性完成自己的責(zé)任。所以該類是承擔(dān)了責(zé)任的,并且該類的責(zé)任又集中到唯一的單體對(duì)象中。而其他對(duì)象又依賴于該特定對(duì)象所承擔(dān)的責(zé)任,我們就需要得到該特定對(duì)象。那該類就是一個(gè)單例模式的實(shí)現(xiàn)了。 注意:服務(wù)器可以使用多個(gè)實(shí)例來處理請(qǐng)求,代替單個(gè)實(shí)例的請(qǐng)求排隊(duì)帶來的效益問題。服務(wù)器創(chuàng)建一個(gè)Servlet類的多個(gè)Servlet實(shí)例組成的實(shí)例池,對(duì)于每個(gè)請(qǐng)求分配Servlet實(shí)例進(jìn)行響應(yīng)處理,之后放回到實(shí)例池中等待下此請(qǐng)求。這樣就造成并發(fā)訪問的問題。
此時(shí),局部變量(字段)也是安全的,但對(duì)于全局變量和共享數(shù)據(jù)是不安全的,需要進(jìn)行同步處理。而對(duì)于這樣多實(shí)例的情況SingleThreadModel接口并不能解決并發(fā)訪問問題。 SingleThreadModel接口在servlet規(guī)范中已經(jīng)被廢棄了。二 如何開發(fā)線程安全的Servlet 1、實(shí)現(xiàn) SingleThreadModel 接口 該接口指定了系統(tǒng)如何處理對(duì)同一個(gè)Servlet的調(diào)用。如果一個(gè)Servlet被這個(gè)接口指定,那么在這個(gè)Servlet中的service方法將不會(huì)有兩個(gè)線程被同時(shí)執(zhí)行,當(dāng)然也就不存在線程安全的問題。這種方法只要將前面的Concurrent Test類的類頭定義更改為: Public
class Concurrent Test
extends HttpServlet
implements SingleThreadModel {
…………
} 2、同步對(duì)共享數(shù)據(jù)的操作 使用synchronized 關(guān)鍵字能保證一次只有一個(gè)線程可以訪問被保護(hù)的區(qū)段,在本論文中的Servlet可以通過同步塊操作來保證線程的安全。同步后的代碼如下:
…………
Public class Concurrent Test
extends HttpServlet { …………
Username = request.getParameter ("username"
);
Synchronized (this){
Output =
response.getWriter ();
Try {
Thread. Sleep (5000
);
} Catch (Interrupted Exception e){}
output.println("用戶名:"+Username+"<BR>"
);
}
}
} 3、避免使用實(shí)例變量 本實(shí)例中的線程安全問題是由實(shí)例變量造成的,只要在Servlet里面的任何方法里面都不使用實(shí)例變量,那么該Servlet就是線程安全的。 修正上面的Servlet代碼,將實(shí)例變量改為局部變量實(shí)現(xiàn)同樣的功能,代碼如下:
……
Public class Concurrent Test
extends HttpServlet {
public void service (HttpServletRequest request, HttpServletResponse
Response) throws ServletException, IOException {
Print Writer output;
String username;
Response.setContentType ("text/html; charset=gb2312"
);
……
}
} ** 對(duì)上面的三種方法進(jìn)行測試,可以表明用它們都能設(shè)計(jì)出線程安全的Servlet程序。但是,如果一個(gè)Servlet實(shí)現(xiàn)了SingleThreadModel接口,Servlet引擎將為每個(gè)新的請(qǐng)求創(chuàng)建一個(gè)單獨(dú)的Servlet實(shí)例,這將引起大量的系統(tǒng)開銷。SingleThreadModel在Servlet2.4中已不再提倡使用;同樣如果在程序中使用同步來保護(hù)要使用的共享的數(shù)據(jù),也會(huì)使系統(tǒng)的性能大大下降。這是因?yàn)楸煌降拇a塊在同一時(shí)刻只能有一個(gè)線程執(zhí)行它,使得其同時(shí)處理客戶請(qǐng)求的吞吐量降低,而且很多客戶處于阻塞狀態(tài)。另外為保證主存內(nèi)容和線程的工作內(nèi)存中的數(shù)據(jù)的一致性,要頻繁地刷新緩存,這也會(huì)大大地影響系統(tǒng)的性能。所以在實(shí)際的開發(fā)中也應(yīng)避免或最小化 Servlet 中的同步代碼;在Serlet中避免使用實(shí)例變量是保證Servlet線程安全的最佳選擇。從Java 內(nèi)存模型也可以知道,方法中的臨時(shí)變量是在棧上分配空間,而且每個(gè)線程都有自己私有的棧空間,所以它們不會(huì)影響線程的安全。
更加詳細(xì)的說明:1,變量的線程安全:這里的變量指字段和共享數(shù)據(jù)(如表單參數(shù)值)。
a,將 參數(shù)變量 本地化。多線程并不共享局部變量.所以我們要盡可能的在servlet中使用局部變量。
例如:String user = "";
user = request.getParameter("user"); b,使用同步塊Synchronized,防止可能異步調(diào)用的代碼塊。這意味著線程需要排隊(duì)處理。在使用同板塊的時(shí)候要盡可能的縮小同步代碼的范圍,不要直接在sevice方法和響應(yīng)方法上使用同步,這樣會(huì)嚴(yán)重影響性能。 2,屬性的線程安全:ServletContext,HttpSession,ServletRequest對(duì)象中屬性。
ServletContext:(線程是不安全的)
ServletContext是可以多線程同時(shí)讀/寫屬性的,線程是不安全的。要對(duì)屬性的讀寫進(jìn)行同步處理或者進(jìn)行深度Clone()。所以在Servlet上下文中盡可能少量保存會(huì)被修改(寫)的數(shù)據(jù),可以采取其他方式在多個(gè)Servlet中共享,比方我們可以使用單例模式來處理共享數(shù)據(jù)。
HttpSession:(線程是不安全的)
HttpSession對(duì)象在用戶會(huì)話期間存在,只能在處理屬于同一個(gè)Session的請(qǐng)求的線程中被訪問,因此Session對(duì)象的屬性訪問理論上是線程安全的。
當(dāng)用戶打開多個(gè)同屬于一個(gè)進(jìn)程的瀏覽器窗口,在這些窗口的訪問屬于同一個(gè)Session,會(huì)出現(xiàn)多次請(qǐng)求,需要多個(gè)工作線程來處理請(qǐng)求,可能造成同時(shí)多線程讀寫屬性。這時(shí)我們需要對(duì)屬性的讀寫進(jìn)行同步處理:使用同步塊Synchronized和使用讀/寫器來解決。
ServletRequest:(線程是安全的)
對(duì)于每一個(gè)請(qǐng)求,由一個(gè)工作線程來執(zhí)行,都會(huì)創(chuàng)建有一個(gè)新的ServletRequest對(duì)象,所以ServletRequest對(duì)象只能在一個(gè)線程中被訪問。ServletRequest是線程安全的。注意:ServletRequest對(duì)象在service方法的范圍內(nèi)是有效的,不要試圖在service方法結(jié)束后仍然保存請(qǐng)求對(duì)象的引用。 4,不要在Servlet中創(chuàng)建自己的線程來完成某個(gè)功能。
Servlet本身就是多線程的,在Servlet中再創(chuàng)建線程,將導(dǎo)致執(zhí)行情況復(fù)雜化,出現(xiàn)多線程安全問題。 5,在多個(gè)servlet中對(duì)外部對(duì)象(比方文件)進(jìn)行修改操作一定要加鎖,做到互斥的訪問。 6,javax.servlet.SingleThreadModel接口是一個(gè)標(biāo)識(shí)接口,如果一個(gè)Servlet實(shí)現(xiàn)了這個(gè)接口,那Servlet容器將保證在一個(gè)時(shí)刻僅有一個(gè)線程可以在給定的servlet實(shí)例的service方法中執(zhí)行。將其他所有請(qǐng)求進(jìn)行排隊(duì)。 PS:
Servlet并非只是單例的. 當(dāng)container開始啟動(dòng),或是客戶端發(fā)出請(qǐng)求服務(wù)時(shí),Container會(huì)按照容器的配置負(fù)責(zé)加載和實(shí)例化一個(gè)Servlet(也可以配置為多個(gè),不過一般不這么干).不過一般來說一個(gè)servlet只會(huì)有一個(gè)實(shí)例。
1) Struts2的Action是原型,非單實(shí)例的;會(huì)對(duì)每一個(gè)請(qǐng)求,產(chǎn)生一個(gè)Action的實(shí)例來處理。
2) Struts1的Action,Spring的Ioc容器管理的bean 默認(rèn)是單實(shí)例的. Struts1 Action是單實(shí)例的,spring mvc的controller也是如此。因此開發(fā)時(shí)要求必須是線程安全的,因?yàn)閮H有Action的一個(gè)實(shí)例來處理所有的請(qǐng)求。單例策略限制了Struts1 Action能作的事,并且要在開發(fā)時(shí)特別小心。Action資源必須是線程安全的或同步的。
Spring的Ioc容器管理的bean 默認(rèn)是單實(shí)例的。
Struts2 Action對(duì)象為每一個(gè)請(qǐng)求產(chǎn)生一個(gè)實(shí)例,因此沒有線程安全問題。(實(shí)際上,servlet容器給每個(gè)請(qǐng)求產(chǎn)生許多可丟棄的對(duì)象,并且不會(huì)導(dǎo)致性能和垃圾回收問題)。
當(dāng)Spring管理Struts2的Action時(shí),bean默認(rèn)是單實(shí)例的,可以通過配置參數(shù)將其設(shè)置為原型。(scope="prototype )Servlet的生命周期:
大致分為4部:Servlet類加載-->實(shí)例化-->服務(wù)-->銷毀
1、Web Client向Servlet容器(Tomcat)發(fā)出Http請(qǐng)求。
2、Servlet容器接收Client端的請(qǐng)求。
3、Servlet容器創(chuàng)建一個(gè)HttpRequest對(duì)象,將Client的請(qǐng)求信息封裝到這個(gè)對(duì)象中。
4、Servlet創(chuàng)建一個(gè)HttpResponse對(duì)象。
5、Servlet調(diào)用HttpServlet對(duì)象的service方法,把HttpRequest對(duì)象和HttpResponse對(duì)象作為參數(shù)傳遞給HttpServlet對(duì)象中。
6、HttpServlet調(diào)用HttpRequest對(duì)象的方法,獲取Http請(qǐng)求,并進(jìn)行相應(yīng)處理。
7、處理完成HttpServlet調(diào)用HttpResponse對(duì)象的方法,返回響應(yīng)數(shù)據(jù)。
8、Servlet容器把HttpServlet的響應(yīng)結(jié)果傳回客戶端。
? ? ? ? 其中的3個(gè)方法說明了Servlet的生命周期:
1、init():負(fù)責(zé)初始化Servlet對(duì)象。
2、service():負(fù)責(zé)響應(yīng)客戶端請(qǐng)求。
3、destroy():當(dāng)Servlet對(duì)象退出時(shí),負(fù)責(zé)釋放占用資源。
Servlet的生命周期由Servlet容器管理;(三個(gè)概念的理解:Servlet容器<Web容器<應(yīng)用服務(wù)器?Servlet容器的主要任務(wù)就是管理Servlet的生命周期;Web容器也稱之為web服務(wù)器,主要任務(wù)就是管理和部署web應(yīng)用的;應(yīng)用服務(wù)器的功能非常強(qiáng)大,不僅可以管理和部署web應(yīng)用,也可以部署EJB應(yīng)用,實(shí)現(xiàn)容器管理的事務(wù)等等。。。Web服務(wù)器就是跟基于HTTP的請(qǐng)求打交道,而EJB容器更多是跟數(shù)據(jù)庫,事務(wù)管理等服務(wù)接口交互,所以應(yīng)用服務(wù)器的功能是很多的。常見的web服務(wù)器就是Tomcat,但Tomcat同樣也是Servlet服務(wù)器;常見的應(yīng)用服務(wù)器有WebLogic,WebSphere,但都是收費(fèi)的;沒有Servlet容器,可以用Web容器直接訪問靜態(tài)Html頁面,比如安裝了apache等;如果需要顯示Jsp/Servlet,就需要安裝一個(gè)Servlet容器;但是光有servlet容器也是不夠的,它需要被解析為html顯示,所以仍需要一個(gè)web容器;所以,我們常把web容器和Servlet容器視為一體,因?yàn)樗麄儍蓚€(gè)容器都有對(duì)方的功能實(shí)現(xiàn)了,都沒有獨(dú)立的存在了,比如tomcat!)
理解兩個(gè)問題足以,問題如下:
1、如果不同的2個(gè)用戶同時(shí)對(duì)這個(gè)網(wǎng)站的不同業(yè)務(wù)同時(shí)發(fā)出請(qǐng)求(如注冊(cè)和登陸),那容器里有幾個(gè)servlet呢??
2、不同的用戶同時(shí)對(duì)同一個(gè)業(yè)務(wù)(如注冊(cè))發(fā)出請(qǐng)求,那這個(gè)時(shí)候容器里產(chǎn)生的有是幾個(gè)servlet實(shí)例呢?
答案:
引子:一個(gè)web容器,可以有多個(gè)servlet。 對(duì)提交到同一個(gè)servlet類的多個(gè)業(yè)務(wù)請(qǐng)求,共享一個(gè)servlet對(duì)象(即這個(gè)servlet類只被實(shí)例化一次)
但別忘了,請(qǐng)求還可以從一個(gè)servlet forward到另一個(gè)servlet,因此一個(gè)請(qǐng)求是可以產(chǎn)生多個(gè)servlet的,但是由不同的servlet類實(shí)例化的,每個(gè)servlet類都只被實(shí)例化一次,直到應(yīng)用程序終止或服務(wù)器shutdown
問題1的答案:容器里有2個(gè)servlet(當(dāng)然,這是在“一個(gè)servlet對(duì)應(yīng)一種業(yè)務(wù)請(qǐng)求”的前提下,如果你要把兩個(gè)業(yè)務(wù)邏輯寫在同一個(gè)servlet內(nèi)另當(dāng)別論了)
問題2的答案:只有一個(gè)servlet實(shí)例。一個(gè)servlet是在第一次被訪問時(shí)加載到內(nèi)存并實(shí)例化的。同樣的業(yè)務(wù)請(qǐng)求共享一個(gè)servlet實(shí)例。不同的業(yè)務(wù)請(qǐng)求一般對(duì)應(yīng)不同的servlet. 想也知道拉,如果一個(gè)網(wǎng)站要被幾千萬人同時(shí)登錄,如果創(chuàng)建幾千萬個(gè)實(shí)例的話服務(wù)器還怎么跑得動(dòng)?
參考:對(duì)java servlet 單例模式的理解
參考:Servlet其實(shí)是單例多線程
總結(jié)
以上是生活随笔為你收集整理的servlet单实例多线程模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。