在上一篇關於Serlvet框架和Servlet生命週期的學習中,咱們已經知道了在多線程的狀況下java
Servlet是線程不安全的。Servlet體系是創建在java多線程的基礎之上的,它的生命週期是由Tomcatweb
來維護的。當客戶端第一次請求Servlet的時候,tomcat會根據web.xml配置文件實例化servlet,瀏覽器
當又有一個客戶端訪問該servlet的時候,不會再實例化該servlet,也就是多個線程在使用這個實例。tomcat
serlvet採用多線程來處理多個請求同時訪問,Tomcat容器維護了一個線程池來服務請求。安全
線程池其實是等待執行代碼的一組線程叫作工做組線程(Worker Thread),Tomcat容器使用一個多線程
調度線程來管理工做組線程(Dispatcher Thead)。併發
當容器收到一個Servlet請求,Dispatcher線程從線程池中選出一個工做組線程,將請求傳遞框架
給該線程,而後由該線程來執行Servlet的service方法。ide
當這個線程正在執行的時候,容器收到另外一個請求,調度者線程將從線程池中選出另一個性能
工做組線程來服務則個新的請求,容器並不關心這個請求是否訪問的是同一個Servlet仍是另外一個
Servlet。當容器收到對同一個Servlet的多個請求的時候,那這個servlet的service方法將在多線程
中併發的執行。
多線程和單線程Servlet具體區別:多線程下每一個線程對局部變量都會有本身的一份copy,這
樣對局部變量的修改只會影響到本身的copy而不會對別的線程產生影響,線程安全的。可是對於
實例變量來講,因爲servlet在Tomcat中是以單例模式存在的,全部的線程共享實例變量。多個線程
對共享資源的訪問就形成了線程不安全問題。
對於單線程而言就不存在這方面的問題(static變量除外)
這裏咱們寫一個實例來模擬一下:
package com.kiritor; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class ThreadServlet */ public class ThreadServlet extends HttpServlet { private static final long serialVersionUID = 1L; private String message; /** * @see HttpServlet#HttpServlet() */ public ThreadServlet() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse * response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse * response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { message = request.getParameter("message"); PrintWriter printWriter = response.getWriter(); try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } printWriter.write(message); } }以後咱們在打開兩個瀏覽器:
http://localhost:8080/Servlet03/ThreadServlet?message=helloA
http://localhost:8080/Servlet03/ThreadServlet?message=helloB
咱們不斷的嘗試刷新瀏覽器,能夠發現的是輸出結果並非咱們想象的那麼簡單,並且錯誤的輸出
是具備偶然性的,這更增長了程序潛在的危險性。
至於其實際的輸出效果筆者就貼圖了,讀者可自行進行演示。
針對上述的狀況如何設計線程安全的Servlet呢?咱們知道的是多線程是不共享局部變量的
servlet線程不安全也是針對於共享資源的訪問才產生的。 所以這裏就有一種方式了。
這裏的變量變量指的是字段和共享數據,主要是表單的參數值。基於多線程不共享局部變量的
特色咱們能夠將這類變量參數本地化。例如對於上面的一個實例咱們能夠這樣設計。
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String message; message = request.getParameter("message"); PrintWriter printWriter = response.getWriter(); try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } printWriter.write(message); }
ServletContext:它是線程不安全的,多線程下能夠同時進行讀寫,所以咱們要對其讀寫操做進行
同步或者深度的clone。
HttpSession:一樣是線程不安全的,和ServletContext的操做同樣。
ServletRequest:它是線程安全的,對於每個請求由一個工做線程來執行,都會建立一個
ServletRequest對象,因此ServletResquest只能在一個線程中被訪問,並且他只在service()方法內是
有效的。
在使用java中的集合API進行處理的時候,選擇同步的集合。
在多個Servlet中對某個外部對象(例如文件)的修改是務必加鎖,互斥訪問。不過這裏須要注意的是
使用Synchronized的時候這意味着線程須要排隊等待處理,所以在使用同步塊的時候要儘可能的縮小同
步塊的代碼範圍。不要直接在方法上用同步,這樣會嚴重影響性能。
值得一提的是最好別再serlvet中建立本身的線程來完成某個功能,這會是狀況更加複雜。
這也是解決servlet線程安全問題的一個方法,Single ThreadMode是一個標識接口,若是一個Servlet
實現了該接口,那麼Tomcat將保證在一個時刻僅有一個線程能夠在給定的Serlvet實例的service方法中
執行。其餘全部請求進行排隊。(針對單個實例)
能夠看出的是這種方式雖然能夠解決線程安全問題,能夠效率太太低下。
其再Servlet的規範中已經被廢棄了。
Servlet的線程安全問題只有在大量的併發訪問時纔會顯現出來,而且很難發現,所以在編寫Servlet程序
時要特別注意。線程安全問題主要是由實例變量形成的,所以在Servlet中應避免使用實例變量。若是應用程
序設計沒法避免使用實例變量,那麼使用同步來保護要使用的實例變量,但爲保證系統的最佳性能,應該
同步可用性最小的代碼路徑。