深刻理解Servlet線程安全問題

        前言

                在上一篇關於Serlvet框架和Servlet生命週期的學習中,咱們已經知道了在多線程的狀況下java

            Servlet是線程不安全的。Servlet體系是創建在java多線程的基礎之上的,它的生命週期是由Tomcatweb

            來維護的。當客戶端第一次請求Servlet的時候,tomcat會根據web.xml配置文件實例化servlet,瀏覽器

            當又有一個客戶端訪問該servlet的時候,不會再實例化該servlet,也就是多個線程在使用這個實例。tomcat

         Servlet線程池

                 serlvet採用多線程來處理多個請求同時訪問,Tomcat容器維護了一個線程池來服務請求。安全

             線程池其實是等待執行代碼的一組線程叫作工做組線程(Worker Thread),Tomcat容器使用一個多線程

             調度線程來管理工做組線程(Dispatcher Thead)。併發

             

                       當容器收到一個Servlet請求,Dispatcher線程從線程池中選出一個工做組線程,將請求傳遞框架

               給該線程,而後由該線程來執行Servlet的service方法。ide

                       當這個線程正在執行的時候,容器收到另外一個請求,調度者線程將從線程池中選出另一個性能

               工做組線程來服務則個新的請求,容器並不關心這個請求是否訪問的是同一個Servlet仍是另外一個

               Servlet。當容器收到對同一個Servlet的多個請求的時候,那這個servlet的service方法將在多線程

               中併發的執行。

           Servlet線程安全問題

                      多線程和單線程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呢?咱們知道的是多線程是不共享局部變量的

              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中建立本身的線程來完成某個功能,這會是狀況更加複雜。

             Single ThreadMode接口

                      這也是解決servlet線程安全問題的一個方法,Single ThreadMode是一個標識接口,若是一個Servlet

                  實現了該接口,那麼Tomcat將保證在一個時刻僅有一個線程能夠在給定的Serlvet實例的service方法中

                  執行。其餘全部請求進行排隊。(針對單個實例)

                       能夠看出的是這種方式雖然能夠解決線程安全問題,能夠效率太太低下。

                       其再Servlet的規範中已經被廢棄了。

            總結

                     Servlet的線程安全問題只有在大量的併發訪問時纔會顯現出來,而且很難發現,所以在編寫Servlet程序

                 時要特別注意。線程安全問題主要是由實例變量形成的,所以在Servlet中應避免使用實例變量。若是應用程

                 序設計沒法避免使用實例變量,那麼使用同步來保護要使用的實例變量,但爲保證系統的最佳性能,應該

                 同步可用性最小的代碼路徑。

相關文章
相關標籤/搜索