一、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容器如何同時來處理多個請求涉及到的概念或原理:
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體系結構是建立在Java多線程機制之上的,它的生命周期是由Web容器負責的。
當客戶端第一次請求某個Servlet時,Servlet容器將會根據web.xml配置文件實例化這個Servlet類。當有新的客戶端請求該Servlet時,一般不會再實例化該Servlet類,也就是有多個線程在使用這個實例。Servlet容器會自動使用線程池等技術來支持系統的運行,如圖1所示。
這樣,當兩個或多個線程同時訪問同一個Servlet時,可能會發生多個線程同時訪問同一資源的情況,數據可能會變得不一致。所以在用Servlet構建的Web應用時如果不注意線程安全的問題,會使所寫的Servlet程序有難以發現的錯誤。
解決此類的方法也有多
1、實現 SingleThreadModel 接口(將引起大量的系統開銷)
該接口指定了系統如何處理對同一個Servlet的調用。如果一個Servlet被這個接口指定,那么在這個Servlet中的service方法將不會有兩個線程被同時執行,當然也就不存在線程安全的問題。這種方法只要繼承這個接口就行了
- public class XXXXX extends HttpServlet implements SingleThreadModel {
- ......
- }
javax.servlet.SingleThreadModel API及其翻譯
Ensures that servlets handle only one request at a time. This interface has no methods.
確保servlet每次只處理一項請求。接口不含方法。
If a servlet implements this interface, you are guaranteed that no two threads will execute concurrently in the servlet's service method. The servlet container can make this guarantee by synchronizing access to a single instance of the servlet, or by maintaining a pool of servlet instances and dispatching each new request to a free servlet.
如果servlet實現了該接口,會確保不會有兩個線程同時執行servlet的service方法。 servlet容器通過同步化訪問servlet的單實例來保證,也可以通過維持servlet的實例池,對於新的請求會分配給一個空閑的servlet。
Note that SingleThreadModel does not solve all thread safety issues. For example, session attributes and static variables can still be accessed by multiple requests on multiple threads at the same time, even when SingleThreadModel servlets are used. It is recommended that a developer take other means to resolve those issues instead of implementing this interface, such as avoiding the usage of an instance variable or synchronizing the block of the code accessing those resources. This interface is deprecated in Servlet API version 2.4.
注意:SingleThreadModel不會解決所有的線程安全隱患。 例如,會話屬性和靜態變量仍然可以被多線程的多請求同時訪問,即便使用了SingleThreadModel servlet。建議開發人員應當采取其他手段來解決這些問題,而不是實現該接口,比如 避免實例變量的使用或者在訪問資源時同步代碼塊。該接口在Servlet API 2.4中將不推薦使用。
2、同步對共享數據的操作:被同步的代碼塊在同一時刻只能有一個線程執行它,使得其同時處理客戶請求的吞吐量降低,而且很多客戶處於阻塞狀態
使用synchronized 關鍵字能保證一次只有一個線程可以訪問被保護的區段,在本論文中可以通過同步塊操作來保證Servlet的線程安全。同步后的代碼如下:
- Public class XXXXXX extends HttpServlet {
- ......
- synchronized (this){XXXX}
- }
3、避免使用實例變量
線程安全問題還有些是由實例變量造成的,只要在Servlet里面的任何方法里面都不使用實例變量,那么該Servlet就是線程安全的。
對上面的三種方法進行測試,可以表明用它們都能設計出線程安全的Servlet程序。但是,如果一個Servlet實現了SingleThreadModel接口,Servlet引擎將為每個新的請求創建一個單獨的Servlet實例,這將引起大量的系統開銷。SingleThreadModel在Servlet2.4中已不再提倡使用;同樣如果在程序中使用同步來保護要使用的共享的數據,也會使系統的性能大大下降。這是因為被同步的代碼塊在同一時刻只能有一個線程執行它,使得其同時處理客戶請求的吞吐量降低,而且很多客戶處於阻塞狀態。另外為保證主存內容和線程的工作內存中的數據的一致性,要頻繁地刷新緩存,這也會大大地影響系統的性能。所以在實際的開發中也應避免或最小化Servlet 中的同步代碼;在Serlet中避免使用實例變量是保證Servlet線程安全的最佳選擇。從Java 內存模型也可以知道,方法中的臨時變量是在棧上分配空間,而且每個線程都有自己私有的棧空間,所以它們不會影響線程的安全。
小結
Servlet的線程安全問題只有在大量的並發訪問時才會顯現出來,並且很難發現,因此在編寫Servlet程序時要特別注意。線程安全問題主要是由實例變量造成的,因此在Servlet中應避免使用實例變量。如果應用程序設計無法避免使用實例變量,那么使用同步來保護要使用的實例變量,但為保證系統的最佳性能,應該同步可用性最小的代碼路徑。
參見:http://www.cnblogs.com/yjhrem/articles/3160864.html
和 http://blog.csdn.net/bruce_6/article/details/39157673

