posts - 104,  comments - 34,  trackbacks - 0

          Servlet體系結(jié)構(gòu)是建立在Java多線程機(jī)制之上的,它的生命周期是由Web容器負(fù)責(zé)的。當(dāng)客戶端第一次請(qǐng)求某個(gè)Servlet時(shí),Servlet容器將會(huì)根據(jù)web.xml配置文件實(shí)例化這個(gè)Servlet類。當(dāng)有新的客戶端請(qǐng)求該Servlet時(shí),一般不會(huì)再實(shí)例化該Servlet類,也就是有多個(gè)線程在使用這個(gè)實(shí)例。 這樣,當(dāng)兩個(gè)或多個(gè)線程同時(shí)訪問同一個(gè)Servlet時(shí),可能會(huì)發(fā)生多個(gè)線程同時(shí)訪問同一資源的情況,數(shù)據(jù)可能會(huì)變得不一致。所以在用Servlet構(gòu)建的Web應(yīng)用時(shí)如果不注意線程安全的問題,會(huì)使所寫的Servlet程序有難以發(fā)現(xiàn)的錯(cuò)誤。

          實(shí)例變量不正確的使用是造成Servlet線程不安全的主要原因。下面針對(duì)該問題給出了三種解決方案并對(duì)方案的選取給出了一些參考性的建議。

            1、實(shí)現(xiàn) SingleThreadModel 接口

            該接口指定了系統(tǒng)如何處理對(duì)同一個(gè)Servlet的調(diào)用。如果一個(gè)Servlet被這個(gè)接口指定,那么在這個(gè)Servlet中的service方法將不會(huì)有兩個(gè)線程被同時(shí)執(zhí)行,當(dāng)然也就不存在線程安全的問題。這種方法只要將前面的Concurrent Test類的類頭定義更改為:

          Public class Concurrent Test extends HttpServlet implements SingleThreadModel {
               …………
          }
            2、同步對(duì)共享數(shù)據(jù)的操作

            使用synchronized 關(guān)鍵字能保證一次只有一個(gè)線程可以訪問被保護(hù)的區(qū)段,在本論文中的Servlet可以通過同步塊操作來保證線程的安全。同步后的代碼如下:

          …………
          Public class Concurrent Test extends HttpServlet { …………
          Username = request.getParameter ("username");
          Synchronized (this){
          Output = response.getWriter ();
          Try {
          Thread. Sleep (5000);
          } Catch (Interrupted Exception e){}
          output.println("用戶名:"+Username+"
          ");
          }
          }
          }
            3、避免使用實(shí)例變量

            本實(shí)例中的線程安全問題是由實(shí)例變量造成的,只要在Servlet里面的任何方法里面都不使用實(shí)例變量,那么該Servlet就是線程安全的。

            修正上面的Servlet代碼,將實(shí)例變量改為局部變量實(shí)現(xiàn)同樣的功能,代碼如下:

          ……
          Public class Concurrent Test extends HttpServlet {public void service (HttpServletRequest request, HttpServletResponse
          Response) throws ServletException, IOException {
          Print Writer output;
          String username;
          Response.setContentType ("text/html; charset=gb2312");
          ……
          }
          }
            對(duì)上面的三種方法進(jìn)行測(cè)試,可以表明用它們都能設(shè)計(jì)出線程安全的Servlet程序。但是,如果一個(gè)Servlet實(shí)現(xiàn)了SingleThreadModel接口,Servlet引擎將為每個(gè)新的請(qǐng)求創(chuàng)建一個(gè)單獨(dú)的Servlet實(shí)例,這將引起大量的系統(tǒng)開銷。SingleThreadModel在Servlet2.4中已不再提倡使用;同樣如果在程序中使用同步來保護(hù)要使用的共享的數(shù)據(jù),也會(huì)使系統(tǒng)的性能大大下降。這是因?yàn)楸煌降拇a塊在同一時(shí)刻只能有一個(gè)線程執(zhí)行它,使得其同時(shí)處理客戶請(qǐng)求的吞吐量降低,而且很多客戶處于阻塞狀態(tài)。另外為保證主存內(nèi)容和線程的工作內(nèi)存中的數(shù)據(jù)的一致性,要頻繁地刷新緩存,這也會(huì)大大地影響系統(tǒng)的性能。所以在實(shí)際的開發(fā)中也應(yīng)避免或最小化 Servlet 中的同步代碼;在Serlet中避免使用實(shí)例變量是保證Servlet線程安全的最佳選擇。從Java 內(nèi)存模型也可以知道,方法中的臨時(shí)變量是在棧上分配空間,而且每個(gè)線程都有自己私有的棧空間,所以它們不會(huì)影響線程的安全。


          --------------------------------------------------------------------------------

          補(bǔ)充:

          servlet存在的多線程問題
          實(shí)例變量:   實(shí)例變量是在堆中分配的,并被屬于該實(shí)例的所有線程共享,所以不是線程安全的.
          JSP系統(tǒng)提供的8個(gè)類變量:
          JSP中用到的OUT,REQUEST,RESPONSE,SESSION,CONFIG,PAGE,PAGECONXT是線程安全的,APPLICATION在整個(gè)系統(tǒng)內(nèi)被使用,所以不是線程安全的.
          局部變量:   局部變量在堆棧中分配,因?yàn)槊總€(gè)線程都有它自己的堆棧空間,所以是線程安全的.
          靜態(tài)類:       靜態(tài)類不用被實(shí)例化,就可直接使用,也不是線程安全的.
          外部資源:   在程序中可能會(huì)有多個(gè)線程或進(jìn)程同時(shí)操作同一個(gè)資源(如:多個(gè)線程或進(jìn)程同時(shí)對(duì)一個(gè)文件進(jìn)行寫操作).

          此時(shí)也要注意同步問題. 使它以單線程方式執(zhí)行,這時(shí),仍然只有一個(gè)實(shí)例,所有客戶端的請(qǐng)求以串行方式執(zhí)行。這樣會(huì)降低系統(tǒng)的性能
          對(duì)于存在線程不安全的類,如何避免出現(xiàn)線程安全問題:
          1、采用synchronized同步。缺點(diǎn)就是存在堵塞問題。
          2、使用ThreadLocal(實(shí)際上就是一個(gè)HashMap),這樣不同的線程維護(hù)自己的對(duì)象,線程之間相互不干擾。

           


          --------------------------------------------------------------------------------


          ThreadLocal的設(shè)計(jì)
          首先看看ThreadLocal的接口:
          Object get() ; // 返回當(dāng)前線程的線程局部變量副本 protected Object
          initialValue(); // 返回該線程局部變量的當(dāng)前線程的初始值                   
          void set(Object value); // 設(shè)置當(dāng)前線程的線程局部變量副本的值
            ThreadLocal有3個(gè)方法,其中值得注意的是initialValue(),該方法是一個(gè)protected
          的方法,顯然是為了子類重寫而特意實(shí)現(xiàn)的。該方法返回當(dāng)前線程在該線程局部變量的初始
          值,這個(gè)方法是一個(gè)延遲調(diào)用方法,在一個(gè)線程第1次調(diào)用get()或者set(Object)時(shí)才執(zhí)行
          ,并且僅執(zhí)行1次。ThreadLocal中的確實(shí)實(shí)現(xiàn)直接返回一個(gè)null:
          protected Object initialValue() { return null; }
            ThreadLocal是如何做到為每一個(gè)線程維護(hù)變量的副本的呢?其實(shí)實(shí)現(xiàn)的思路很簡(jiǎn)單,
          在ThreadLocal類中有一個(gè)Map,用于存儲(chǔ)每一個(gè)線程的變量的副本。比如下面的示例實(shí)現(xiàn):

          public class ThreadLocal
          {
           private Map values = Collections.synchronizedMap(new HashMap());
           public Object get()
           {
            Thread curThread = Thread.currentThread();
            Object o = values.get(curThread);
            if (o == null && !values.containsKey(curThread))
            {
             o = initialValue();
             values.put(curThread, o);
            }
            return o;
           }

           public void set(Object newValue)
           {
            values.put(Thread.currentThread(), newValue);
           }

           public Object initialValue()
           {
            return null;
           }
          }

            當(dāng)然,這并不是一個(gè)工業(yè)強(qiáng)度的實(shí)現(xiàn),但JDK中的ThreadLocal的實(shí)現(xiàn)總體思路也類似于此。
          ThreadLocal的使用
            如果希望線程局部變量初始化其它值,那么需要自己實(shí)現(xiàn)ThreadLocal的子類并重寫該
          方法,通常使用一個(gè)內(nèi)部匿名類對(duì)ThreadLocal進(jìn)行子類化,比如下面的例子,SerialNum類
          為每一個(gè)類分配一個(gè)序號(hào):
          public class SerialNum
          {
           // The next serial number to be assigned
           private static int nextSerialNum = 0;
           private static ThreadLocal serialNum = new ThreadLocal()
           {
            protected synchronized Object initialValue()
            {
             return new Integer(nextSerialNum++);
            }
           };

           public static int get()
           {
            return ((Integer) (serialNum.get())).intValue();
           }
          }

            SerialNum類的使用將非常地簡(jiǎn)單,因?yàn)間et()方法是static的,所以在需要獲取當(dāng)前線
          程的序號(hào)時(shí),簡(jiǎn)單地調(diào)用:

          int serial = SerialNum.get(); 即可。
            在線程是活動(dòng)的并且ThreadLocal對(duì)象是可訪問的時(shí),該線程就持有一個(gè)到該線程局部
          變量副本的隱含引用,當(dāng)該線程運(yùn)行結(jié)束后,該線程擁有的所以線程局部變量的副本都將失
          效,并等待垃圾收集器收集。
          ThreadLocal與其它同步機(jī)制的比較
            ThreadLocal和其它同步機(jī)制相比有什么優(yōu)勢(shì)呢?ThreadLocal和其它所有的同步機(jī)制都
          是為了解決多線程中的對(duì)同一變量的訪問沖突,在普通的同步機(jī)制中,是通過對(duì)象加鎖來實(shí)
          現(xiàn)多個(gè)線程對(duì)同一變量的安全訪問的。這時(shí)該變量是多個(gè)線程共享的,使用這種同步機(jī)制需
          要很細(xì)致地分析在什么時(shí)候?qū)ψ兞窟M(jìn)行讀寫,什么時(shí)候需要鎖定某個(gè)對(duì)象,什么時(shí)候釋放該
          對(duì)象的鎖等等很多。所有這些都是因?yàn)槎鄠€(gè)線程共享了資源造成的。ThreadLocal就從另一
          個(gè)角度來解決多線程的并發(fā)訪問,ThreadLocal會(huì)為每一個(gè)線程維護(hù)一個(gè)和該線程綁定的變
          量的副本,從而隔離了多個(gè)線程的數(shù)據(jù),每一個(gè)線程都擁有自己的變量副本,從而也就沒有
          必要對(duì)該變量進(jìn)行同步了。ThreadLocal提供了線程安全的共享對(duì)象,在編寫多線程代碼時(shí)
          ,可以把不安全的整個(gè)變量封裝進(jìn)ThreadLocal,或者把該對(duì)象的特定于線程的狀態(tài)封裝進(jìn)
          ThreadLocal。
            由于ThreadLocal中可以持有任何類型的對(duì)象,所以使用ThreadLocal get當(dāng)前線程的值
          是需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換。但隨著新的Java版本(1.5)將模版的引入,新的支持模版參數(shù)
          的ThreadLocal<T>類將從中受益。也可以減少強(qiáng)制類型轉(zhuǎn)換,并將一些錯(cuò)誤檢查提前到了編
          譯期,將一定程度地簡(jiǎn)化ThreadLocal的使用。
          總結(jié)
              當(dāng)然ThreadLocal并不能替代同步機(jī)制,兩者面向的問題領(lǐng)域不同。同步機(jī)制是為了同
          步多個(gè)線程對(duì)相同資源的并發(fā)訪問,是為了多個(gè)線程之間進(jìn)行通信的有效方式;而
          ThreadLocal是隔離多個(gè)線程的數(shù)據(jù)共享,從根本上就不在多個(gè)線程之間共享資源(變量)
          ,這樣當(dāng)然不需要對(duì)多個(gè)線程進(jìn)行同步了。所以,如果你需要進(jìn)行多個(gè)線程之間進(jìn)行通信,
          則使用同步機(jī)制;如果需要隔離多個(gè)線程之間的共享沖突,可以使用ThreadLocal,這將極
          大地簡(jiǎn)化你的程序,使程序更加易讀、簡(jiǎn)潔。

          ThreadLocal常見用途:
          存放當(dāng)前session用戶
          存放一些context變量,比如webwork的ActionContext
          存放session,比如Spring hibernate orm的session


          例子:用 ThreadLocal 實(shí)現(xiàn)每線程 Singleton
                  線程局部變量常被用來描繪有狀態(tài)“單子”(Singleton) 或線程安全的共享對(duì)象,或者是通過把不安全的整個(gè)變量封裝進(jìn) ThreadLocal,或者是通過把對(duì)象的特定于線程的狀態(tài)封裝進(jìn) ThreadLocal。例如,在與數(shù)據(jù)庫有緊密聯(lián)系的應(yīng)用程序中,程序的很多方法可能都需要訪問數(shù)據(jù)庫。在系統(tǒng)的每個(gè)方法中都包含一個(gè) Connection 作為參數(shù)是不方便的 — 用“單子”來訪問連接可能是一個(gè)雖然更粗糙,但卻方便得多的技術(shù)。然而,多個(gè)線程不能安全地共享一個(gè) JDBC Connection。如清單 3 所示,通過使用“單子”中的 ThreadLocal,我們就能讓我們的程序中的任何類容易地獲取每線程 Connection 的一個(gè)引用。這樣,我們可以認(rèn)為 ThreadLocal 允許我們創(chuàng)建每線程單子。
          例:把一個(gè) JDBC 連接存儲(chǔ)到一個(gè)每線程 Singleton 中
          public class ConnectionDispenser {
            private static class ThreadLocalConnection extends ThreadLocal {
                     public Object initialValue() {
                            return DriverManager.getConnection(ConfigurationSingleton.getDbUrl());
                     }
             }
              private ThreadLocalConnection conn = new ThreadLocalConnection();
              public static Connection getConnection() {
                      return (Connection) conn.get();
             }
          }

          注意:
          理論上來說,ThreadLocal是的確是相對(duì)于每個(gè)線程,每個(gè)線程會(huì)有自己的ThreadLocal。但是上面已經(jīng)講到,一般的應(yīng)用服務(wù)器都會(huì)維護(hù)一套線程池。因此,不同用戶訪問,可能會(huì)接受到同樣的線程。因此,在做基于TheadLocal時(shí),需要謹(jǐn)慎,避免出現(xiàn)ThreadLocal變量的緩存,導(dǎo)致其他線程訪問到本線程變量。

           

          本文來自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/Explorering/archive/2006/10/11/1330744.aspx

          posted on 2009-09-08 15:50 末日風(fēng)情 閱讀(1094) 評(píng)論(0)  編輯  收藏 所屬分類: JSP基礎(chǔ)

          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          <2009年9月>
          303112345
          6789101112
          13141516171819
          20212223242526
          27282930123
          45678910

          常用鏈接

          留言簿(4)

          隨筆分類

          隨筆檔案

          搜索

          •  

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 江安县| 天镇县| 闵行区| 龙里县| 望江县| 安丘市| 额敏县| 全州县| 房山区| 永安市| 婺源县| 隆回县| 蓝山县| 甘孜县| 商丘市| 安平县| 收藏| 忻州市| 普安县| 陆丰市| 平顶山市| 泰安市| 正阳县| 成安县| 马山县| 宿松县| 华宁县| 北安市| 孝昌县| 江安县| 乌兰浩特市| 清水河县| 光山县| 华蓥市| 阜新| 涟水县| 沙田区| 子长县| 陕西省| 察隅县| 雷波县|