深入理解Servlet線程安全問題

        前言

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

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

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

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

         Servlet線程池

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

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

             調度線程來管理工作組線程(Dispatcher Thead)。

             

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

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

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

               工作組線程來服務則個新的請求,容器並不關心這個請求是否訪問的是同一個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中應避免使用實例變量。如果應用程

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

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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章