<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    敬的世界

    常用鏈接

    統計

    最新評論

    J2EE的線程安全-ThreadLocal

    源自?。骸?a >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 閱讀(471) 評論(0)  編輯  收藏


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


    網站導航:
     
    主站蜘蛛池模板: 久久亚洲国产精品一区二区| 亚洲国产精品成人AV在线| 午夜免费国产体验区免费的| 色播在线永久免费视频| 亚洲精品国产自在久久| 丁香六月婷婷精品免费观看| 又粗又黄又猛又爽大片免费 | 亚洲成a人在线看天堂无码| 精品久久久久久亚洲综合网| a级片免费在线观看| 国产精品亚洲аv无码播放| 韩日电影在线播放免费版| 四虎国产精品免费久久| 精品亚洲国产成人| 免费无码看av的网站| 看免费毛片天天看| 国产亚洲真人做受在线观看| 亚洲熟妇AV日韩熟妇在线| 成人免费无码大片A毛片抽搐| 亚洲今日精彩视频| 99久久精品日本一区二区免费| 亚洲无码高清在线观看| 热久久这里是精品6免费观看| 免费一级毛片在级播放| 亚美影视免费在线观看| 亚洲va无码手机在线电影| 成人午夜免费福利视频| 亚洲精品天堂成人片AV在线播放| 无码囯产精品一区二区免费| 亚洲国产美女福利直播秀一区二区 | 亚洲 综合 国产 欧洲 丝袜| 亚洲国产亚洲片在线观看播放| a级片免费在线观看| 91亚洲国产成人久久精品| 在线免费一区二区| 国产精品免费看久久久香蕉| 亚洲一区综合在线播放| 超pen个人视频国产免费观看| 亚洲综合一区国产精品| 免费大片黄在线观看yw| 高潮毛片无遮挡高清免费视频|