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

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

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

    隨筆-4  評(píng)論-15  文章-0  trackbacks-0
    不知如何轉(zhuǎn)帖,只有ctrl +v了

    Servlet線程安全探討

    閱讀(67) 評(píng)論(0) 發(fā)表時(shí)間:2008年11月20日 15:46

    本文地址:http://qzone.qq.com/blog/190658200-1227167200

    本文標(biāo)簽: Servlet 線程安全
    我們?cè)陂_(kāi)發(fā)JAVA WEB應(yīng)用程序,大多都會(huì)考慮用MVC模式的框架來(lái)部署(相信沒(méi)有程序員再考慮前兩者簡(jiǎn)單的模式了吧。),這也是基于下面的原因:
    一、MVC由于分層清晰,容易看到整個(gè)系統(tǒng)流程的架構(gòu),對(duì)于越來(lái)越復(fù)雜的系統(tǒng)是有相當(dāng)大的幫助。
    二、 擴(kuò)展性,耦合性低。各層影響相當(dāng)少,如JSP頁(yè)面只負(fù)責(zé)數(shù)據(jù)顯示,M負(fù)責(zé)業(yè)務(wù)邏輯處理,C相當(dāng)于Servlet來(lái)控制流程的轉(zhuǎn)向。等等這一系列的好處。
    MVC的模式,其本質(zhì)就是用Servlet的應(yīng)用技術(shù)Servlet/jsp和其他如ASP\PHP語(yǔ)言相比,由于使用了多線程運(yùn)行技術(shù)與具有很高的執(zhí)行效率。但是也就是Servlet由于默認(rèn)多線程模式執(zhí)行,依我們所了解線程安全性問(wèn)題,也就不得不要考慮在Servlet中也存在這樣的問(wèn)題,然而,很多程序員只專注于業(yè)務(wù)邏輯的處理,并沒(méi)有注意到多線程的安全性的問(wèn)題(在此編寫(xiě)之前,我也存在這樣的經(jīng)歷,不過(guò)還好。。。。),這往往造成編寫(xiě)的程序在用戶量少的時(shí)候沒(méi)出什么問(wèn)題,而一旦發(fā)現(xiàn)大量的并發(fā)用戶時(shí),而且這數(shù)量達(dá)到一定的數(shù)量時(shí),就會(huì)出現(xiàn)一系列莫名的問(wèn)題,這問(wèn)題在下面我們可以看的到。
    Servlet的多線程機(jī)制是怎么樣的呢:
    Servlet體系結(jié)構(gòu)是建立在JAVA的多線程機(jī)制上的,但它的生命周期是由WEB容器來(lái)管理的,當(dāng)客戶端第一次請(qǐng)求某個(gè)Servlet時(shí),Servlet容器會(huì)根據(jù)web.xml的配置實(shí)例化相應(yīng)的Servlet類,當(dāng)有新的客戶端來(lái)請(qǐng)求這個(gè)Servlet時(shí),容器一般不會(huì)再實(shí)例化這個(gè)Servlet類,而是以線程方式去調(diào)用這個(gè)實(shí)例的方法,然后再有更多的客戶端來(lái)請(qǐng)求時(shí),就存在了多個(gè)線程在使用這個(gè)實(shí)例。并且Servlet容器會(huì)自動(dòng)使用線程池技術(shù)來(lái)支持系統(tǒng)的運(yùn)行。
    在這樣的情況,當(dāng)兩個(gè)或者多個(gè)客戶端同時(shí)請(qǐng)求同一Serlvet時(shí),就會(huì)存在多個(gè)線程同時(shí)訪問(wèn)同一資源的情況,數(shù)據(jù)就可能變的不一致,所以在用Servlet搭建WEB應(yīng)用程序時(shí)如果不考慮線程的問(wèn)題,就會(huì)出現(xiàn)難以發(fā)現(xiàn)的問(wèn)題。
    Servlet線程安全問(wèn)題的例子:
    Servlet線程是由于使用實(shí)例變量不當(dāng)而導(dǎo)致的,這里有如下的例子:
    代碼程序如下:
    public class SecurityTest extends HttpServlet {
        PrintWriter output;//成員變量

        @Override
        protected void service(HttpServletRequest request,
               HttpServletResponse response) throws ServletException,
    IOException {
           response.setContentType("text/html;charset=gb2312");
           String name = request.getParameter("name");
           output=response.getWriter();
           try {
               Thread.sleep(5000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           output.write(name);
        }

    }

    該實(shí)例中定義了一個(gè)實(shí)例變量output,在service方法中負(fù)責(zé)輸出用戶名,當(dāng)一個(gè)用戶訪問(wèn)該Servlet時(shí),程序會(huì)正常的運(yùn)行,但當(dāng)多個(gè)用戶并發(fā)的訪問(wèn)時(shí),就可能會(huì)出現(xiàn)其他的用戶信息顯示在另一個(gè)用戶的瀏覽器上的問(wèn)題,為了看到實(shí)際的效果,在這個(gè)程序中,我特做如下的處理:就是延時(shí)5000毫秒,讓第一個(gè)用戶暫停在輸出數(shù)據(jù)前。然后我們馬上發(fā)起另一個(gè)請(qǐng)求,這種情況下就會(huì)出現(xiàn)如下的頁(yè)面
    第一次請(qǐng)求:
    http://127.0.0.1:8080/Test2/securityTest?name=a

    大家看到這頁(yè)面什么數(shù)據(jù)也沒(méi)有,就是說(shuō)那姓名沒(méi)有打印出來(lái),那跑到哪里去了呢?看第二個(gè)用戶請(qǐng)求的情況。
    第二個(gè)用戶請(qǐng)求地址:http://127.0.0.1:8080/Test2/securityTest?name=b

    可以看到,原來(lái)a值已經(jīng)打印到第二個(gè)用戶的瀏覽器了。
    可以想像在暫停5000時(shí)間里,第二個(gè)用戶請(qǐng)求這個(gè)servlet,已經(jīng)把output的引用變成了第二個(gè)用戶請(qǐng)求的output值了,這樣就解釋了為什么會(huì)輸出到第二個(gè)客戶端的瀏覽的原因。

    從內(nèi)存模型來(lái)看Servlet的線程安全問(wèn)題//不是很理解這一段
       JAVA的內(nèi)存模型JMM(JAVA Memory Model)主要是為了規(guī)定線程與內(nèi)存的一些關(guān)系,既然Servlet也是用線程技術(shù),那么我們也從這方面尋找根本原因,根據(jù)JMM,系統(tǒng)存在有主內(nèi)存,JAVA的實(shí)例變量(就是類變量吧)都是存在于主內(nèi)存供其他內(nèi)存使用,也就是對(duì)所有的線程都是共享的,而根據(jù)線程的特點(diǎn):它是有自己的工作內(nèi)存的,工作內(nèi)存包括緩存和堆棧兩部分,堆棧是專門用來(lái)存在方法中的局部變量的,而緩存則是主內(nèi)存中變量的拷貝,緩存與主內(nèi)存并不總是同步的,也就是緩存中的變量的修改可能沒(méi)有立刻寫(xiě)到主存中,如下圖:

    根據(jù)內(nèi)存模型,我們可以得到如下的線程調(diào)度表:
    調(diào)度時(shí)刻
    a線程
    b線程
    T1
    訪問(wèn)Servlet頁(yè)面
    T2
    訪問(wèn)Servlet頁(yè)面
    T3
    output=a的輸出username=a休眠5000毫秒,讓出CPU
    T4
    output=b的輸出(寫(xiě)回主存)username=b休眠5000毫秒,讓出CPU
    T5
    在用戶b的瀏覽器上輸出a線程的username的值,a線程終止。
    T6
    在用戶b的瀏覽器上輸出b線程的username的值,b線程終止。

    可以看出,由于b線程對(duì)實(shí)例變量output的修改覆蓋了a線程對(duì)實(shí)例變量output的值,直接導(dǎo)致了用戶a的信息顯示到b的瀏覽器上。根據(jù)內(nèi)存模型,我們也可以解釋到,正是因?yàn)?font style="line-height: 1.5em" size="3">b開(kāi)始時(shí)修改了緩存中的output值,然后刷新到主內(nèi)存中,而又有足夠的時(shí)間刷新到a緩存中,這時(shí)aOutput值就直接導(dǎo)致了指向b瀏覽器。
    解決方法:
    從上面的分析中,我們知道導(dǎo)致線程不安全的主要原因在于實(shí)例變量的使用不當(dāng),下面就提出如下三種解決方法
    第一,Servlet類實(shí)現(xiàn)SingleThreadModel接口,該接口指定系統(tǒng)如何處理對(duì)同一個(gè)Servlet的調(diào)用,如果一個(gè)Servlet被指定實(shí)現(xiàn)這個(gè)接口,那么,在這個(gè)Servlet中的service方法將不會(huì)在兩個(gè)線程中同時(shí)執(zhí)行,也就是說(shuō)執(zhí)行完一個(gè)后再執(zhí)行下一個(gè)請(qǐng)求的service,當(dāng)然也就不存在線程不安全的問(wèn)題了。
    代碼如下:
    public class SecurityTest extends HttpServlet implements SingleThreadModel{}
    其實(shí)這方法也就相當(dāng)于是同步方法的效果吧。

    第二.同步對(duì)共享數(shù)據(jù)的操作。我們所熟悉的就是用synchronized關(guān)鍵字,這樣能保證一次只有一個(gè)線程來(lái)操作被保護(hù)的區(qū)段。在本例子也可以用synchronized來(lái)保證線程的安全,代碼如下:
    public class SecurityTest extends HttpServlet {
        PrintWriter output;

        @Override
        protected void service(HttpServletRequest request,
               HttpServletResponse response) throws ServletException, IOException {
           response.setContentType("text/html;charset=gb2312");
           String name = request.getParameter("name");

    Synchronized(this){
           output=response.getWriter();
           try {
               Thread.sleep(5000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           output.write(name);
    }
        }}
    第三:避免使用實(shí)例變量,而使用局部變量。因?yàn)镾ervlet線程不安全的原因是由實(shí)例變量引起,所以我們可以避免使用實(shí)例變量,而使用局部變量,線程之間很難直接訪問(wèn)局部變量 ,這樣就從根本上解決了這一問(wèn)題。
    在本例子中,就是將output放在service方法中當(dāng)局部變量 。
    public class SecurityTest extends HttpServlet {

        @Override
        protected void service(HttpServletRequest request,
               HttpServletResponse response) throws ServletException, IOException {
            PrintWriter output;
           response.setContentType("text/html;charset=gb2312");
           String name = request.getParameter("name");
           output=response.getWriter();
           try {
               Thread.sleep(5000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           output.write(name);
        }}

    這三種方法都對(duì)解決servlet線程安全起到很好的作用,但我們?nèi)绻麑?duì)他們進(jìn)行比較一下,看哪一種更適合呢:
    第一個(gè)方案中:實(shí)現(xiàn)SingleThreadModel接口,Servlet引擎將為每個(gè)客戶請(qǐng)求都生成一個(gè)Servlet實(shí)例,這將引起大量的系統(tǒng)開(kāi)銷,在新版本的Servlet2.4中也不提倡使用了。
    第二個(gè)方案中:在程序中使用同步來(lái)保護(hù)要使用的共享數(shù)據(jù),也使系統(tǒng)的性能大大的下降,這是因?yàn)楸煌降拇a在同一時(shí)刻只能由一個(gè)線程來(lái)執(zhí)行,使得同時(shí)處理其他客戶請(qǐng)求的吞吐量大大降低,大量客戶處于阻塞狀態(tài),這對(duì)于并發(fā)用戶請(qǐng)求來(lái)說(shuō)并非是一件很好的事情。另外為了保持主內(nèi)存與工作內(nèi)存數(shù)據(jù)的一致性要頻繁地刷新緩存,這也大大降低了系統(tǒng)性能,所以這種方案不大可取。
    第三個(gè)方案則應(yīng)該是最優(yōu)方案:從JAVA內(nèi)存模型來(lái)看,方法中的臨時(shí)變量都是在棧中分配空間,而每個(gè)線程都有自己的私有棧空間,互不干擾,不會(huì)影響性能,也不會(huì)產(chǎn)生線程安全的問(wèn)題。
    posted on 2009-04-23 16:31 王業(yè)平 閱讀(397) 評(píng)論(0)  編輯  收藏

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


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 一级特黄录像视频免费| 1区2区3区产品乱码免费| 日本亚洲视频在线| 免费可以看黄的视频s色| 全部一级一级毛片免费看| 久久香蕉国产线看观看亚洲片| 国产在线a免费观看| 4hu四虎免费影院www| 亚洲黄色一级毛片| 亚洲Av无码乱码在线观看性色| 久久成人免费电影| 疯狂做受xxxx高潮视频免费| 久久久久久a亚洲欧洲AV| 全免费一级毛片在线播放| 久久免费观看国产精品88av| 亚洲av成人无码网站…| 亚洲a一级免费视频| 免费在线观看中文字幕| 1000部无遮挡拍拍拍免费视频观看 | 97免费人妻无码视频| 免费在线观看一区| 亚洲入口无毒网址你懂的| 亚洲人成图片小说网站| 国产一区二区三区在线免费| 久久w5ww成w人免费| 最近中文字幕免费大全| 亚洲AV成人无码网站| 亚洲国产日韩在线人成下载| 亚洲人成网站在线播放vr | 亚洲精品在线电影| 国产亚洲精品福利在线无卡一| 成人午夜18免费看| 精品无码免费专区毛片| a级毛片免费全部播放| 美女黄频视频大全免费的| 亚洲入口无毒网址你懂的| 91大神亚洲影视在线| 亚洲成a人片在线观看日本| 亚洲国产精品无码久久青草| 日韩电影免费在线| 成人黄软件网18免费下载成人黄18免费视频 |