Servlet線程安全


本文主要來源 zwchen的博客:http://zwchen.iteye.com/blog/91088

概述
在探討java線程安全前,讓我們先簡要介紹一下Java語言。

任何語言,如C++,C#,Java,它們都有相通之處,特別是語法,但如果有人問你,Java語言的核心是什么?類庫?關鍵字?語法?似乎都不 是。Java語言的核心,也就是Sun始終不願意開源的東西:Java虛擬機的實現(不過sun公開了其Java虛擬機規范),也就有了BEA的 JRockit,IBM的Jikes,Sun的Hotspot。

Java的核心有兩點,Java類加載(Java Class Loader)和Java內存管理,它們具體體現在Java類庫的以下幾個類:

java.lang.ClassLoader(java.lang.Class):我們調用的類,包括其接口和超類,import的類是怎么被Java虛擬機載入的?為什么static的字段在servlet容器里面可以一直生存下去(Spring容器中)?

java.lang.Thread(java.lang.ThreadLocal):垃圾回收是怎么進行的(垃圾回收線程)?我們的程序是怎么退出的?

java.lang.refelect.Proxy(java.lang.refelect.Method):為什么Tomcat、 Tapestry、Webwork、Spring等容器和框架可以通過配置文件來調用我們寫的類?Servlet規范、JSF規范、EJB規范、JDBC 規范究竟是怎么回事?為什么它們幾乎都是一些接口,而不是具體類?


Servlet線程安全

在Java的server side開發過程中,線程安全(Thread Safe)是一個尤為突出的問題。因為容器,如Servlet、EJB等一般都是多線程運行的。雖然在開發過程中,我們一般不考慮這些問題,但診斷問題 (Robust),程序優化(Performance),我們必須深入它們。

什么是線程安全?

引用
Thread-safe describes a program portion or routine that can be called from multiple programming threads without unwanted interaction between the threads。



在Java里,線程安全一般體現在兩個方面:

  1、多個thread對同一個java實例的訪問(read和modify)不會相互干擾,它主要體現在關鍵字synchronized。如 ArrayList和Vector,HashMap和Hashtable(后者每個方法前都有synchronized關鍵字)。如果你在 interator一個List對象時,其它線程remove一個element,問題就出現了。

  2、每個線程都有自己的字段,而不會在多個線程之間共享。它主要體現在java.lang.ThreadLocal類,而沒有Java關鍵字支持,如像static、transient那樣。

一個普遍的疑問,我們的Servlet中能夠像JavaBean那樣declare instance或static字段嗎?如果不可以?會引發什么問題?

答案是:不可以。我們下面以實例講解:

首先,我們寫一個普通的Servlet,里面有instance字段count:

  web.xml  >>

1 <servlet>
2     <servlet-name>SimpleServlet</servlet-name>
3     <servlet-class>servlet.SimpleServlet</servlet-class>
4 </servlet>
5 <servlet-mapping>
6     <servlet-name>SimpleServlet</servlet-name>
7     <url-pattern>/SimpleServlet</url-pattern>
8 </servlet-mapping>

  SimpleServlet  >>

 1 public class SimpleServlet extends HttpServlet {
 2      private int counter = 0;  
 3      @Override
 4     protected void service(HttpServletRequest request, HttpServletResponse response)
 5             throws ServletException, IOException {
 6         response.getWriter().println("<HTML><BODY>");
 7         response.getWriter().println(this + " ==> ");
 8         response.getWriter().println(Thread.currentThread() + ": <br>"); 
 9         for(int c=0;c<10;c++){
10             response.getWriter().println("Counter = " + counter + "<BR>");
11             try {
12                 Thread.sleep(1000);  
13                 counter++;  
14             } catch (Exception e) {
15                 e.printStackTrace();
16             }
17         }
18         response.getWriter().println("</BODY></HTML>");
19     }
20 }

  test.html  >>

1 <HTML>  
2     <BODY>  
3         <TABLE>  
4             <TR>  
5                 <TD><IFRAME src="SimpleServlet" name="servlet1" height="200%"> </IFRAME></TD>  
6             </TR>  
7         </TABLE>  
8     </BODY>  
9 </HTML>  

   大家應該發現,test.html寫的和zwchen的博客原文中的寫的有點區別,本來也是按照zwchen的博客原文中的去測試的,但是相信很多人並沒有得出理想的結果,正如博客下面評論上5樓所說的:“沒有出現線程安全問題,數字的順序都是正確的”,我也是如此(我用的是Firefox瀏覽器)。后來換了IE瀏覽器進行測試出現下面的問題,在頁面上只顯示出了第一個<tr></tr>里面的內容,於是我的處理方法就是:test.html的內容如上所示,打開3個IE瀏覽器,同時在瀏覽器中輸入:

  a: http://localhost:8080/ServletTest/SimpleServlet
  b: http://localhost:8080/ServletTest/SimpleServlet

 

  c: http://localhost:8080/ServletTest/SimpleServlet 

   測試結果如下:

        

   我們會發現三點:

  1、Servlet是一個單例對象(Singleton),因為我們看到多次請求的this指針所有打印出來的hashCode值都相同。
  2、servlet在不同的線程(線程池)中運行,如http-8080-1,http-8080-2,http-8080-3 等輸出值可以明顯區分出不同的線程執行了不同一段Servlet邏輯代碼。
  3、count變量在不同的線程中共享,而且它的值被不同的線程修改,輸出時已經不是順序輸出。也就是說,其他的線程會篡改當前線程中實例變量的值,針對這些對象的訪問不是線程安全的。


  上面的結果,違反了線程安全的兩個方面。

  那么,我們怎樣保證按照我們期望的結果運行呢?首先,我想保證產生的count都是順序執行的。
  我們將Servlet代碼重構如下:

 

 1 public class SimpleServlet extends HttpServlet {
 2      private int counter = 0;  
 3      private String mutex = ""; 
 4      @Override
 5     protected void service(HttpServletRequest request, HttpServletResponse response)
 6             throws ServletException, IOException {
 7         response.getWriter().println("<HTML><BODY>");
 8         response.getWriter().println(this + " ==> ");
 9         response.getWriter().println(Thread.currentThread() + ": <br>"); 
10         synchronized (mutex){
11             for(int c=0;c<10;c++){
12                 response.getWriter().println("Counter = " + counter + "<BR>");
13                 try {
14                     Thread.sleep(1000);  
15                     counter++;  
16                 } catch (Exception e) {
17                     e.printStackTrace();
18                 }
19             }
20         }
21         response.getWriter().println("</BODY></HTML>");
22     }
23 }

  這符合了我們的要求,輸出都是按順序的,這正式synchronized的含義。

  附帶說一下,我現在synchronized的是一個字符串變量mutex,不是this對象,這主要是從performance和 Scalability考慮。Synchronized用在this對象上,會帶來嚴重的可伸縮性的問題(Scalability),所有的並發請求都要排隊!

 

 

 

 


免責聲明!

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



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