Servlet如何處理多個請求訪問? Servlet容器默認是採用單實例多線程的方式處理多個請求的: 1.當web服務器啓動的時候(或客戶端發送請求到服務器時),Servlet就被加載並實例化(只存在一個Servlet實例); 2.容器初始化化Servlet主要就是讀取配置文件(例如tomcat,能夠經過servlet.xml的<Connector>設置線程池中線程數目,初始化線程池經過web.xml,初始化每一個參數值等等。 3.當請求到達時,Servlet容器經過調度線程(Dispatchaer Thread) 調度它管理下線程池中等待執行的線程(Worker Thread)給請求者; 4.線程執行Servlet的service方法; 5.請求結束,放回線程池,等待被調用; (注意:避免使用實例變量(成員變量),由於若是存在成員變量,可能發生多線程同時訪問該資源時,都來操做它,照成數據的不一致,所以產生線程安全問題)
從上面能夠看出:
第一:Servlet單實例,減小了產生servlet的開銷;
第二:經過線程池來響應多個請求,提升了請求的響應時間;
第三:Servlet容器並不關心到達的Servlet請求訪問的是不是同一個Servlet仍是另外一個Servlet,直接分配給它一個新的線程;若是是同一個Servlet的多個請求,那麼Servlet的service方法將在多線程中併發的執行;
第四:每個請求由ServletRequest對象來接受請求,由ServletResponse對象來響應該請求;
Servlet/JSP技術和ASP、PHP等相比,因爲其多線程運行而具備很高的執行效率。因爲Servlet/JSP默認是以多線程模式執行的,因此,在編寫代碼時須要很是細緻地考慮多線程的安全性問題。
JSP的中存在的多線程問題:
當客戶端第一次請求某一個JSP文件時,服務端把該JSP編譯成一個CLASS文件,並建立一個該類的實例,而後建立一個線程處理CLIENT端的請求。若是有多個客戶端同時請求該JSP文件,則服務端會建立多個線程。每一個客戶端請求對應一個線程。以多線程方式執行可大大下降對系統的資源需求,提升系統的併發量及響應時間.
對JSP中可能用的的變量說明以下:
實例變量: 實例變量是在堆中分配的,並被屬於該實例的全部線程共享,因此不是線程安全的.
JSP系統提供的8個類變量
JSP中用到的OUT,REQUEST,RESPONSE,SESSION,CONFIG,PAGE,PAGECONXT是線程安全的(由於每一個線程對應的request,respone對象都是不同的,不存在共享問題), APPLICATION在整個系統內被使用,因此不是線程安全的.
局部變量: 局部變量在堆棧中分配,由於每一個線程都有它本身的堆棧空間,因此是線程安全的.
靜態類: 靜態類不用被實例化,就可直接使用,也不是線程安全的.
外部資源: 在程序中可能會有多個線程或進程同時操做同一個資源(如:多個線程或進程同時對一個文件進行寫操做).此時也要注意同步問題.
使它以單線程方式執行,這時,仍然只有一個實例,全部客戶端的請求以串行方式執行。這樣會下降系統的性能
問題
問題一. 說明其Servlet容器如何採用單實例多線程的方式來處理請求
問題二. 如何在開發中保證servlet是單實例多線程的方式來工做(也就是說如何開發線程安全的servelt)。
一. Servlet容器如何同時來處理多個請求
Java的內存模型JMM(Java Memory Model)
JMM主要是爲了規定了線程和內存之間的一些關係。根據JMM的設計,系統存在一個主內存(Main Memory),Java中全部實例變量都儲存在主存中,對於全部線程都是共享的。每條線程都有本身的工做內存(Working Memory),工做內存由緩存和堆棧兩部分組成,緩存中保存的是主存中變量的拷貝,緩存可能並不總和主存同步,也就是緩存中變量的修改可能沒有馬上寫到主存中;堆棧中保存的是線程的局部變量,線程之間沒法相互直接訪問堆棧中的變量。根據JMM,咱們能夠將論文中所討論的Servlet實例的內存模型抽象爲圖所示的模型。
工做者線程Work Thread:執行代碼的一組線程。
調度線程Dispatcher Thread:每一個線程都具備分配給它的線程優先級,線程是根據優先級調度執行的。
Servlet採用多線程來處理多個請求同時訪問。servlet依賴於一個線程池來服務請求。線程池其實是一系列的工做者線程集合。Servlet使用一個調度線程來管理工做者線程。
當容器收到一個Servlet請求,調度線程從線程池中選出一個工做者線程,將請求傳遞給該工做者線程,而後由該線程來執行Servlet的service方法。當這個線程正在執行的時候,容器收到另一個請求,調度線程一樣從線程池中選出另外一個工做者線程來服務新的請求,容器並不關心這個請求是否訪問的是同一個Servlet.當容器同時收到對同一個Servlet的多個請求的時候,那麼這個Servlet的service()方法將在多線程中併發執行。
Servlet容器默認採用單實例多線程的方式來處理請求,這樣減小產生Servlet實例的開銷,提高了對請求的響應時間,對於Tomcat能夠在server.xml中經過<Connector>元素設置線程池中線程的數目。
就實現來講:
調度者線程類所擔負的責任如其名字,該類的責任是調度線程,只須要利用本身的屬性完成本身的責任。因此該類是承擔了責任的,而且該類的責任又集中到惟一的單體對象中。而其餘對象又依賴於該特定對象所承擔的責任,咱們就須要獲得該特定對象。那該類就是一個單例模式的實現了。
注意:服務器可使用多個實例來處理請求,代替單個實例的請求排隊帶來的效益問題。服務器建立一個Servlet類的多個Servlet實例組成的實例池,對於每一個請求分配Servlet實例進行響應處理,以後放回到實例池中等待下此請求。這樣就形成併發訪問的問題。
此時,局部變量(字段)也是安全的,但對於全局變量和共享數據是不安全的,須要進行同步處理。而對於這樣多實例的狀況SingleThreadModel接口並不能解決併發訪問問題。 SingleThreadModel接口在servlet規範中已經被廢棄了。
二 如何開發線程安全的Servlet
一、實現 SingleThreadModel 接口
該接口指定了系統如何處理對同一個Servlet的調用。若是一個Servlet被這個接口指定,那麼在這個Servlet中的service方法將不會有兩個線程被同時執行,固然也就不存在線程安全的問題。這種方法只要將前面的Concurrent Test類的類頭定義更改成:
Public class Concurrent Test extends HttpServlet implements SingleThreadModel { ………… }
二、同步對共享數據的操做 使用synchronized 關鍵字能保證一次只有一個線程能夠訪問被保護的區段,在本論文中的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>"); } } }
三、避免使用實例變量 本實例中的線程安全問題是由實例變量形成的,只要在Servlet裏面的任何方法裏面都不使用實例變量,那麼該Servlet就是線程安全的。 修正上面的Servlet代碼,將實例變量改成局部變量實現一樣的功能,代碼以下:
…… 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"); …… } }
** 對上面的三種方法進行測試,能夠代表用它們都能設計出線程安全的Servlet程序。可是,若是一個Servlet實現了SingleThreadModel接口,Servlet引擎將爲每一個新的請求建立一個單獨的Servlet實例,這將引發大量的系統開銷。SingleThreadModel在Servlet2.4中已再也不提倡使用;一樣若是在程序中使用同步來保護要使用的共享的數據,也會使系統的性能大大降低。這是由於被同步的代碼塊在同一時刻只能有一個線程執行它,使得其同時處理客戶請求的吞吐量下降,並且不少客戶處於阻塞狀態。另外爲保證主存內容和線程的工做內存中的數據的一致性,要頻繁地刷新緩存,這也會大大地影響系統的性能。因此在實際的開發中也應避免或最小化 Servlet 中的同步代碼;在Serlet中避免使用實例變量是保證Servlet線程安全的最佳選擇。從Java 內存模型也能夠知道,方法中的臨時變量是在棧上分配空間,並且每一個線程都有本身私有的棧空間,因此它們不會影響線程的安全。 更加詳細的說明: 1,變量的線程安全:這裏的變量指字段和共享數據(如表單參數值)。 a,將 參數變量 本地化。多線程並不共享局部變量.因此咱們要儘量的在servlet中使用局部變量。 例如:String user = ""; user = request.getParameter("user"); b,使用同步塊Synchronized,防止可能異步調用的代碼塊。這意味着線程須要排隊處理。在使用同板塊的時候要儘量的縮小同步代碼的範圍,不要直接在sevice方法和響應方法上使用同步,這樣會嚴重影響性能。 2,屬性的線程安全:ServletContext,HttpSession,ServletRequest對象中屬性。 ServletContext:(線程是不安全的) ServletContext是能夠多線程同時讀/寫屬性的,線程是不安全的。要對屬性的讀寫進行同步處理或者進行深度Clone()。因此在Servlet上下文中儘量少許保存會被修改(寫)的數據,能夠採起其餘方式在多個Servlet中共享,比方咱們可使用單例模式來處理共享數據。 HttpSession:(線程是不安全的) HttpSession對象在用戶會話期間存在,只能在處理屬於同一個Session的請求的線程中被訪問,所以Session對象的屬性訪問理論上是線程安全的。 當用戶打開多個同屬於一個進程的瀏覽器窗口,在這些窗口的訪問屬於同一個Session,會出現屢次請求,須要多個工做線程來處理請求,可能形成同時多線程讀寫屬性。這時咱們須要對屬性的讀寫進行同步處理:使用同步塊Synchronized和使用讀/寫器來解決。 ServletRequest:(線程是安全的) 對於每個請求,由一個工做線程來執行,都會建立有一個新的ServletRequest對象,因此ServletRequest對象只能在一個線程中被訪問。ServletRequest是線程安全的。注意:ServletRequest對象在service方法的範圍內是有效的,不要試圖在service方法結束後仍然保存請求對象的引用。 4,不要在Servlet中建立本身的線程來完成某個功能。 Servlet自己就是多線程的,在Servlet中再建立線程,將致使執行狀況複雜化,出現多線程安全問題。 5,在多個servlet中對外部對象(比方文件)進行修改操做必定要加鎖,作到互斥的訪問。 6,javax.servlet.SingleThreadModel接口是一個標識接口,若是一個Servlet實現了這個接口,那Servlet容器將保證在一個時刻僅有一個線程能夠在給定的servlet實例的service方法中執行。將其餘全部請求進行排隊。 PS: Servlet並不是只是單例的. 當container開始啓動,或是客戶端發出請求服務時,Container會按照容器的配置負責加載和實例化一個Servlet(也能夠配置爲多個,不過通常不這麼幹).不過通常來講一個servlet只會有一個實例。 1) Struts2的Action是原型,非單實例的;會對每個請求,產生一個Action的實例來處理。 2) Struts1的Action,Spring的Ioc容器管理的bean 默認是單實例的. Struts1 Action是單實例的,spring mvc的controller也是如此。所以開發時要求必須是線程安全的,由於僅有Action的一個實例來處理全部的請求。單例策略限制了Struts1 Action能做的事,而且要在開發時特別當心。Action資源必須是線程安全的或同步的。 Spring的Ioc容器管理的bean 默認是單實例的。 Struts2 Action對象爲每個請求產生一個實例,所以沒有線程安全問題。(實際上,servlet容器給每一個請求產生許多可丟棄的對象,而且不會致使性能和垃圾回收問題)。 當Spring管理Struts2的Action時,bean默認是單實例的,能夠經過配置參數將其設置爲原型。(scope="prototype ) Servlet的生命週期:
大體分爲4部:Servlet類加載-->實例化-->服務-->銷燬html
一、Web Client向Servlet容器(Tomcat)發出Http請求。java
二、Servlet容器接收Client端的請求。web
三、Servlet容器建立一個HttpRequest對象,將Client的請求信息封裝到這個對象中。spring
四、Servlet建立一個HttpResponse對象。數據庫
五、Servlet調用HttpServlet對象的service方法,把HttpRequest對象和HttpResponse對象做爲參數傳遞給HttpServlet對象中。apache
六、HttpServlet調用HttpRequest對象的方法,獲取Http請求,並進行相應處理。瀏覽器
七、處理完成HttpServlet調用HttpResponse對象的方法,返回響應數據。緩存
八、Servlet容器把HttpServlet的響應結果傳回客戶端。tomcat
其中的3個方法說明了Servlet的生命週期:安全
一、init():負責初始化Servlet對象。
二、service():負責響應客戶端請求。
三、destroy():當Servlet對象退出時,負責釋放佔用資源。
Servlet的生命週期由Servlet容器管理; (三個概念的理解: Servlet容器<Web容器<應用服務器? Servlet容器的主要任務就是管理Servlet的生命週期; Web容器也稱之爲web服務器,主要任務就是管理和部署web應用的; 應用服務器的功能很是強大,不只能夠管理和部署web應用,也能夠部署EJB應用,實現容器管理的事務等等。。。 Web服務器就是跟基於HTTP的請求打交道,而EJB容器更可能是跟數據庫,事務管理等服務接口交互,因此應用服務器的功能是不少的。 常見的web服務器就是Tomcat,但Tomcat一樣也是Servlet服務器; 常見的應用服務器有WebLogic,WebSphere,但都是收費的; 沒有Servlet容器,能夠用Web容器直接訪問靜態Html頁面,好比安裝了apache等;若是須要顯示Jsp/Servlet,就須要安裝一個Servlet容器;可是光有servlet容器也是不夠的,它須要被解析爲html顯示,因此仍須要一個web容器;因此,咱們常把web容器和Servlet容器視爲一體,由於他們兩個容器都有對方的功能實現了,都沒有獨立的存在了,好比tomcat! )
理解兩個問題足以,問題以下:
一、若是不一樣的2個用戶同時對這個網站的不一樣業務同時發出請求(如註冊和登錄),那容器裏有幾個servlet呢??
二、不一樣的用戶同時對同一個業務(如註冊)發出請求,那這個時候容器裏產生的有是幾個servlet實例呢?
答案:
引子:一個web容器,能夠有多個servlet。 對提交到同一個servlet類的多個業務請求,共享一個servlet對象(即這個servlet類只被實例化一次)
但別忘了,請求還能夠從一個servlet forward到另外一個servlet,所以一個請求是能夠產生多個servlet的,可是由不一樣的servlet類實例化的,每一個servlet類都只被實例化一次,直到應用程序終止或服務器shutdown
問題1的答案:容器裏有2個servlet(固然,這是在「一個servlet對應一種業務請求」的前提下,若是你要把兩個業務邏輯寫在同一個servlet內另當別論了)
問題2的答案:只有一個servlet實例。一個servlet是在第一次被訪問時加載到內存並實例化的。一樣的業務請求共享一個servlet實例。不一樣的業務請求通常對應不一樣的servlet. 想也知道拉,若是一個網站要被幾千萬人同時登陸,若是建立幾千萬個實例的話服務器還怎麼跑得動?