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中應避免使用實例變量。如果應用程

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM