敬的世界

          常用鏈接

          統計

          最新評論

          J2EE的線程安全-ThreadLocal

          源自 : http://makeitjoy.javaeye.com/blog/252298

          1.在用Servlet時,我們都知道Servlet只會被初始化一次,只有一個實例。

          2.在Struts1中,ActionServlet也僅是初始化一次,也是單實例。

          為什么會這樣,無非是為了提高效率。但是線程安全不容忽視。但是在WebWork、Struts2卻做好了線程安全。

          下面具體介紹:

          Servlet線程安全

          概述

          在探討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:
          Java代碼 復制代碼
          1. public?class?SimpleServlet?extends?HttpServlet ??
          2. { ??
          3. ????//?A?variable?that?is?NOT?thread-safe! ??
          4. ????private?int?counter?=?0; ??
          5. ??
          6. ????public?void?doGet(HttpServletRequest?req,?HttpServletResponse?resp) ??
          7. ????????????throws?ServletException,?IOException ??
          8. ????{ ??
          9. ????????doPost(req,?resp); ??
          10. ????} ??
          11. ??
          12. ????public?void?doPost(HttpServletRequest?req,?HttpServletResponse?resp) ??
          13. ????????????throws?ServletException,?IOException ??
          14. ????{ ??
          15. ????????resp.getWriter().println("<HTML><BODY>"); ??
          16. ????????resp.getWriter().println(this?+?"?==>?"); ??
          17. ????????resp.getWriter().println(Thread.currentThread()?+?":?<br>"); ??
          18. ???????? ??
          19. ????????for?(int?c?=?0;?c?<?10;?c++) ??
          20. ????????{ ??
          21. ????????????resp.getWriter().println("Counter?=?"?+?counter?+?"<BR>"); ??
          22. ????????????try??
          23. ????????????{ ??
          24. ????????????????Thread.sleep((long)?Math.random()?*?1000); ??
          25. ????????????????counter++; ??
          26. ????????????} ??
          27. ????????????catch?(InterruptedException?exc) ??
          28. ????????????{ ??
          29. ????????????} ??
          30. ????????} ??
          31. ????????resp.getWriter().println("</BODY></HTML>"); ??
          32. ????} ??
          33. }??


          然后,我們通過一個html頁面向該servlet發出三次請求:
          Html代碼 復制代碼
          1. <HTML>??
          2. <BODY>??
          3. <TABLE>??
          4. <TR>??
          5. <TD><IFRAME?src="./SimpleServlet"?name="servlet1"?height="200%">?</IFRAME></TD>??
          6. </TR>??
          7. <TR>??
          8. ??
          9. <TD><IFRAME?src="./SimpleServlet"?name="servlet2"?height="200%">?</IFRAME></TD>??
          10. ??
          11. </TR>??
          12. <TR>??
          13. ??
          14. <TD><IFRAME?src="./SimpleServlet"?name="servlet3"?height="200%">?</IFRAME></TD>??
          15. ??
          16. </TR>??
          17. ??
          18. </TABLE>??
          19. </BODY>??
          20. </HTML>??
          ????

          刷新頁面幾次后,產生的結果為:

          Java代碼 復制代碼
          1. com.zwchen.servlet.SimpleServlet@11e1bbf?==>?Thread[http-8081-Processor23,5,main]:? ??
          2. Counter?=?60??
          3. Counter?=?61??
          4. Counter?=?62??
          5. Counter?=?65??
          6. Counter?=?68??
          7. Counter?=?71??
          8. Counter?=?74??
          9. Counter?=?77??
          10. Counter?=?80??
          11. Counter?=?83??
          12. ??
          13. ??
          14. ??
          15. com.zwchen.servlet.SimpleServlet@11e1bbf?==>?Thread[http-8081-Processor22,5,main]:? ??
          16. Counter?=?61??
          17. Counter?=?63??
          18. Counter?=?66??
          19. Counter?=?69??
          20. Counter?=?72??
          21. Counter?=?75??
          22. Counter?=?78??
          23. Counter?=?81??
          24. Counter?=?84??
          25. Counter?=?87??
          26. ??
          27. ??
          28. ??
          29. com.zwchen.servlet.SimpleServlet@11e1bbf?==>?Thread[http-8081-Processor24,5,main]:? ??
          30. Counter?=?61??
          31. Counter?=?64??
          32. Counter?=?67??
          33. Counter?=?70??
          34. Counter?=?73??
          35. Counter?=?76??
          36. Counter?=?79??
          37. Counter?=?82??
          38. Counter?=?85??
          39. Counter?=?88??



          我們會發現三點:

          servlet只產生了一個Servlet對象,因為輸出this時,其hashcode都一樣,

          servlet在不同的線程(線程池)中運行,如http-8081-Processor22,http-8081-Processor23

          Count被這三個doGet方法共享,并且并行修改。


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

          那么,我們怎樣保證按照我們期望的結果運行呢?首先,我想保證產生的count都是順序執行的。

          我們將Servlet代碼重構如下:


          Java代碼 復制代碼
          1. public?class?SimpleServlet?extends?HttpServlet ??
          2. ??
          3. { ??
          4. ????//?A?variable?that?is?NOT?thread-safe! ??
          5. ????private?int?counter?=?0; ??
          6. ????private?String?mutex?=?""; ??
          7. ???? ??
          8. ????public?void?doGet(HttpServletRequest?req,?HttpServletResponse?resp) ??
          9. ????throws?ServletException,?IOException ??
          10. ????{ ??
          11. ????????doPost(req,?resp); ??
          12. ????} ??
          13. ??
          14. ????public?void?doPost(HttpServletRequest?req,?HttpServletResponse?resp) ??
          15. ????throws?ServletException,?IOException ??
          16. ????{ ??
          17. ????????resp.getWriter().println("<HTML><BODY>"); ??
          18. ????????resp.getWriter().println(this?+?":?<br>"); ??
          19. ????????synchronized?(mutex) ??
          20. ????????{ ??
          21. ????????????for?(int?c?=?0;?c?<?10;?c++) ??
          22. ????????????{ ??
          23. ????????????????resp.getWriter().println("Counter?=?"?+?counter?+?"<BR>"); ??
          24. ????????????????try??
          25. ????????????????{ ??
          26. ????????????????????Thread.sleep((long)?Math.random()?*?1000); ??
          27. ????????????????????counter++; ??
          28. ????????????????} ??
          29. ????????????????catch?(InterruptedException?exc)?{ ??
          30. ????????????????} ??
          31. ????????????} ??
          32. ????????} ??
          33. ????????resp.getWriter().println("</BODY></HTML>"); ??
          34. ????} ??
          35. }??

          我們的輸出結果為:

          Java代碼 復制代碼
          1. com.zwchen.servlet.SimpleServlet@109da93:? ??
          2. Counter?=?0??
          3. Counter?=?1??
          4. Counter?=?2??
          5. Counter?=?3??
          6. Counter?=?4??
          7. Counter?=?5??
          8. Counter?=?6??
          9. Counter?=?7??
          10. Counter?=?8??
          11. Counter?=?9??
          12. ??
          13. ??
          14. ??
          15. com.zwchen.servlet.SimpleServlet@109da93:? ??
          16. Counter?=?10??
          17. Counter?=?11??
          18. Counter?=?12??
          19. Counter?=?13??
          20. Counter?=?14??
          21. Counter?=?15??
          22. Counter?=?16??
          23. Counter?=?17??
          24. Counter?=?18??
          25. Counter?=?19??
          26. ??
          27. ??
          28. ??
          29. com.zwchen.servlet.SimpleServlet@109da93:? ??
          30. Counter?=?20??
          31. Counter?=?21??
          32. Counter?=?22??
          33. Counter?=?23??
          34. Counter?=?24??
          35. Counter?=?25??
          36. Counter?=?26??
          37. Counter?=?27??
          38. Counter?=?28??
          39. Counter?=?29??



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

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

          現在,我們保證了順序,但是我們怎么保證Counter字段(不是局部變量!)在每個Servlet的線程下都是獨立的呢?也就是說,并發請求時,它們都不相互干擾。

          我現在將Servlet代碼重構如下:
          Java代碼 復制代碼
          1. public?class?SimpleServlet?extends?HttpServlet ??
          2. { ??
          3. ????private?ThreadLocal?counter?=?new?ThreadLocal()?{ ??
          4. ????????protected?synchronized?Object?initialValue() ??
          5. ????????{ ??
          6. ????????????return?new?Integer(0); ??
          7. ????????} ??
          8. ????}; ??
          9. ??
          10. ????public?void?doGet(HttpServletRequest?req,?HttpServletResponse?resp) ??
          11. ????????????throws?ServletException,?IOException ??
          12. ????{ ??
          13. ????????doPost(req,?resp); ??
          14. ????} ??
          15. ??
          16. ????public?void?doPost(HttpServletRequest?req,?HttpServletResponse?resp) ??
          17. ????????????throws?ServletException,?IOException ??
          18. ????{ ??
          19. ????????resp.getWriter().println("<HTML><BODY>"); ??
          20. ????????resp.getWriter().println( ??
          21. ????????????????this?+?"["?+?Thread.currentThread()?+?"]:?<br>"); ??
          22. ????????for?(int?c?=?0;?c?<?10;?c++) ??
          23. ????????{ ??
          24. ????????????resp.getWriter().println(counter.get()?+?"<br>"); ??
          25. ????????????try??
          26. ????????????{ ??
          27. ????????????????Thread.sleep((long)?Math.random()?*?1000); ??
          28. ????????????????int?c1?=?((Integer)?counter.get()).intValue(); ??
          29. ????????????????c1++; ??
          30. ????????????????counter.set(new?Integer(c1)); ??
          31. ????????????} ??
          32. ????????????catch?(InterruptedException?exc) ??
          33. ????????????{ ??
          34. ????????????} ??
          35. ????????} ??
          36. ??
          37. ????????resp.getWriter().println("</BODY></HTML>"); ??
          38. ????} ??
          39. }??

          現在,我刷新html頁面三次,第三次結果如下:

          Java代碼 復制代碼
          1. com.zwchen.servlet.SimpleServlet@124e935[Thread[http-8081-Processor22,5,main]]:? ??
          2. 20??
          3. 21??
          4. 22??
          5. 23??
          6. 24??
          7. 25??
          8. 26??
          9. 27??
          10. 28??
          11. 29??
          12. ??
          13. com.zwchen.servlet.SimpleServlet@124e935[Thread[http-8081-Processor25,5,main]]:? ??
          14. 20??
          15. 21??
          16. 22??
          17. 23??
          18. 24??
          19. 25??
          20. 26??
          21. 27??
          22. 28??
          23. 29??
          24. ??
          25. com.zwchen.servlet.SimpleServlet@124e935[Thread[http-8081-Processor23,5,main]]:? ??
          26. 20??
          27. 21??
          28. 22??
          29. 23??
          30. 24??
          31. 25??
          32. 26??
          33. 27??
          34. 28??
          35. 29??



          從以上結果,我們可以發現:

          1、在該html頁面內的并發三次請求中,該Servlet里面的counter字段都不相互干擾

          2、counter字段還是實例字段,并且都會保留狀態,不是每次都用0開始

          3、html頁面內的三次請求都在不同的線程,但在同一個實例中。

          總之,在Java里面,字段(不是局部變量)有三個共享范圍:instance field,static field,local thread field,而后者往往在服務器端這種多線程環境必須考慮到的。

          ??

          在J2EE項目開發過程中,ThreadLocal類有時有非常重要的作用,下面是我碰到的,但可以延伸:

          1、在用Hibernate做web開發的持久化時,有個模式叫做Open Session In View,也就是將session保留到頁面中,在response結束后,在OpenSessionInViewFilter中關閉session,這對于延遲加載非常有效,例如,我們在頁面上顯示User的詳細信息,需要顯示該user的所屬Department的信息; 但是,在list users這種不需要顯示department信息的地方,那個user的department信息就不會加載,也就是說加載相關信息是動態的,但不會出現LazyInitializationException,也就是Load on demand。不過,注意慎用該模式。

          2、在工作流開發,例如OSWorflow,每次調用其服務前,都需要將caller對象傳入,這樣會導致我們的方法非常臃腫,如果我們在調用該方法的上層,如在Servlet里調用它之前,將User對象置于ThreadLocal中,那么可以在工作流方法內通過get()方法獲取,而不用傳入參數。

          3、為什么Web框架中,Webwork的action中可以有field,但Struts卻不能?其實,也就是說,Struts不是線程安全的,而Webwork是線程安全的。大家可以參考Webwork的ActionContext類:

          Java代碼 復制代碼
          1. public?class?ActionContext?implements?Serializable?{ ??
          2. ??
          3. static?ThreadLocal?actionContext?=?new?ActionContextThreadLocal(); ??
          4. ??
          5. ……………??



          而對于Struts,我們可以從ActionServlet.process() => RequestProcessor. processActionPerform,在RequestProcessor中有字段?? protected HashMap actions = new HashMap();我們不難發現,我們所寫的action是共享的,那么內部字段必然也是共享。注意,這種共享類似于Servlet里面的字段。

          posted on 2009-09-11 22:06 picture talk 閱讀(472) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 神农架林区| 合江县| 华安县| 辽宁省| 门源| 百色市| 肇州县| 彭州市| 遂溪县| 睢宁县| 青川县| 资中县| 那坡县| 普定县| 来凤县| 尚义县| 克拉玛依市| 秦皇岛市| 全南县| 呼玛县| 新民市| 伊宁县| 什邡市| 抚远县| 大关县| 望城县| 临漳县| 林芝县| 农安县| 玛纳斯县| 西平县| 东乡族自治县| 普格县| 额尔古纳市| 永寿县| 中卫市| 西宁市| 丹凤县| 昌都县| 施秉县| 连平县|