標籤:service struts 安裝 同步問題 完成 上下文 hashtable 互動 生命週期
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 1、實現 SingleThreadModel 介面 該介面指定了系統如何處理對同一個Servlet的調用。如果一個Servlet被這個介面指定,那麼在這個Servlet中的service方法將不會有兩個線程被同時執行,當然也就不存線上程安全的問題。這種方法只要將前面的Concurrent Test類的類頭定義更改為: Public class Concurrent Test extends HttpServlet implements SingleThreadModel { ………… } 2、同步對共用資料的操作 使用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>"); } } } 3、避免使用執行個體變數 本執行個體中的安全執行緒問題是由執行個體變數造成的,只要在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方法結束後仍然儲存請求對象的引用。 3,使用同步的集合類: 使用Vector代替ArrayList,使用Hashtable代替HashMap。 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的生命週期:1. Servlet在web伺服器啟動時被載入並執行個體化,容器運行其init方法初始化,請求到達時運行其service方法;2. service運行請求對應的doXXX(doGet,doPost)方法;3. 伺服器銷毀執行個體,運行其destory方法;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!)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的的多個請求到來時,如果該Servlet中存在成員變數,可能發生多線程同時訪問該資源時,都來操作它,造成資料的不一致,因此產生安全執行緒問題。解決:1. 實現SingleThreadModel介面如果一個Servlet被這個介面指定,那麼在這個Servlet中的service方法將不會有兩個線程被同時執行,當然也就不存線上程安全的問題;2. 同步對共用資料的操作使用synchronized關鍵字能保證一次只有一個線程可以訪問被保護的區段,Servlet可以通過同步塊操作來保證線程的安全。ServletRequest對象是安全執行緒的,
(原文地址:http://www.cnblogs.com/yjhrem/articles/3160864.html)
[轉]Servlet 單例多線程