Servlet線程不安全是如何體現的?


   在這個遍地框架的年代,我相信很多人對於底層的Servlet的深入了解肯定很少,但是大家肯定對於Servlet的線程安全肯定有所涉獵,也都能講個大概。我也和大家一樣,在一次與同事之間

的閑聊時,談到了Servlet(PS:現在的工作中應用的框架就只有spring,其他倆個框架沒有用,用的是Servlet。)突然說到線程安全的問題,我不假思索的說句,Servlet之所以線程不安全,是

因為Servlet共享了一個實例變量,所以在多線程的環境下容易產生線程不安全的問題。但是同事卻拋出了另外一個問題:Servlet線程不安全的確是因為多線程環境下共享一個實例變量導致

的,在你設計一個Servlet的時候應該如何去避免線程安全問題呢?也許很多同學會順口的答出用鎖,鎖住這個實例變量就能保證在多線程環境下的線程安全問題。但現在的要求是不能鎖住該實

例變量,你能辦到嗎?這個就是知其然不知其所以然,所以變換一種要求就會顯得無所適從。

先從Servlet的工作原理說起:首先簡單解釋一下Servlet接收和響應客戶請求的過程,首先客戶發送一個請求,Servlet是調用service()方法對請求進行響應的,通過源代碼可見,service()方

法中對請求的方式進行了匹配,選擇調用doGet,doPost等這些方法,然后再進入對應的方法中調用邏輯層的方法,實現對客戶的響應。在Servlet接口和GenericServlet中是沒doGet,doPost

等等這些方法的,HttpServlet中定義了這些方法,但是都是返回error信息,所以,我們每次定義一個Servlet的時候,都必須實現doGet或doPost等這些方法。

  每一個自定義的Servlet都必須實現Servlet的接口,Servlet接口中定義了五個方法,其中比較重要的三個方法涉及到Servlet的生命周期,分別是上文提到的init(),service(),destroy()方

法。GenericServlet是一個通用的,不特定於任何協議的Servlet,它實現了Servlet接口。而HttpServlet繼承於GenericServlet,因此HttpServlet也實現了Servlet接口。所以我們定Servle

t的時候只需要繼承HttpServlet即可。

  Servlet接口和GenericServlet是不特定於任何協議的,而HttpServlet是特定於HTTP協議的類,所以HttpServlet中實現了service()方法,並將請求ServletRequest,ServletResponse

強轉為HttpRequest和HttpResponse。在Servlet整個生命周期中是由Tomcat來維護的,當客戶端第一次發起請求的時候,會根據web.xml文件中的配置實例化一個Servlet,而在以后客戶

端的每一次請求都會使用該實例來處理后續的工作,知道Tomcat停止該項目,這個Servlet才會被銷毀,所占用的資源才會釋放。

  當客戶端發來多個請求的時候,Servlet將采用多線程來解決這樣的並發,而在Tomcat本身也維護了一個線程池來處理並發。線程池實際上是等待執行代碼的一組線程叫做工作組線程

(Worker Thread),Tomcat容器使用一個調度線程來管理工作組線程(Dispatcher Thead)。

              

我們還是來寫個簡單的Servlet實例來模擬下:

 1 import java.io.IOException;
 2 import java.io.PrintWriter;
 3 
 4 import javax.servlet.http.HttpServlet;
 5 import javax.servlet.http.HttpServletRequest;
 6 import javax.servlet.http.HttpServletResponse;
 7 
 8 
 9 /**
10  * @author zWX240091
11  *
12  */
13 public class HelloWorldServlet extends HttpServlet
14 {
15     String message;
16     /**
17      * 
18      */
19     private static final long serialVersionUID = 787553024399133588L;
20     public void service(HttpServletRequest request,HttpServletResponse response) throws IOException{
21         message =request.getParameter("message");
22         PrintWriter pw = response.getWriter();
23         try
24         {
25             Thread.sleep(5000);
26         }
27         catch (InterruptedException e)
28         {
29             e.printStackTrace();
30         }
31         pw.write("<div><strong>Hello World</strong>!</div>"+message);
32         pw.close();
33     }
34     
35 }

在構建好的工程部署到本地Tomcat下,啟動Tomcat,在打開倆個瀏覽器

分別訪問1: http://localhost:8080/Servlet03/HelloWorld?message=helloA

分別訪問2: http://localhost:8080/Servlet03/HelloWorld?message=helloB

然后分別刷新訪問,然后你會驚喜的發現在訪問第一個地址的頁面打印出了helloB,在訪問第二個地址的時候頁面有時候會打印出helloA。這個就是高並發下的多線程的安全問題。

那如何不用加鎖的情況下如何讓高並發多線程的環境下線程安全呢?不知道大家注意到沒有,在這個Servlet類里面,message這個屬性是屬於HelloWorldServlet這個實例變量,也就是說這

個實例變量是一個共享的實例變量,那么他所包含所有的屬性都被這個實例變量共享了造成數據不匹配。我們是否可以將共享實例變量下的屬性寫到局部變量去呢?答案是可以的,將上面那段

代碼中的屬性message搬到Service中,在安全來的操作在訪問下,你就會發現永遠不會出現在傳遞過來的參數是helloA的時候頁面會展示給你的是helloB,這樣也就保證了能夠線程安全了。

當然利用鎖也是能夠保證線程安全的,至於性能,就需要看你在什么樣的環境去選擇相應的方法去保證線程安全了。后面Google了下為什么局部變量為什么不會被共享,解釋如下:多線程下每

個線程對局部變量都會有自己的一份copy,這樣對局部變量的修改只會影響到自己的copy而不會對別的線程產生影響,線程安全的。但是對於實例變量來說,由於servlet在Tomcat中是以單

例模式存在的,所有的線程共享實例變量。多個線程對共享資源的訪問就造成了線程不安全問題。

資料參考地址:深入理解Servlet線程安全問題http://blog.csdn.net/lcore/article/details/8974590

       Servlet生命周期與工作原理http://www.cnblogs.com/cuiliang/archive/2011/10/21/2220671.html

 


免責聲明!

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



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