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

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

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

    隨筆-4  評論-15  文章-0  trackbacks-0
    不知如何轉帖,只有ctrl +v了

    Servlet線程安全探討

    閱讀(67) 評論(0) 發表時間:2008年11月20日 15:46

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

    本文標簽: Servlet 線程安全
    我們在開發JAVA WEB應用程序,大多都會考慮用MVC模式的框架來部署(相信沒有程序員再考慮前兩者簡單的模式了吧。),這也是基于下面的原因:
    一、MVC由于分層清晰,容易看到整個系統流程的架構,對于越來越復雜的系統是有相當大的幫助。
    二、 擴展性,耦合性低。各層影響相當少,如JSP頁面只負責數據顯示,M負責業務邏輯處理,C相當于Servlet來控制流程的轉向。等等這一系列的好處。
    MVC的模式,其本質就是用Servlet的應用技術Servlet/jsp和其他如ASP\PHP語言相比,由于使用了多線程運行技術與具有很高的執行效率。但是也就是Servlet由于默認多線程模式執行,依我們所了解線程安全性問題,也就不得不要考慮在Servlet中也存在這樣的問題,然而,很多程序員只專注于業務邏輯的處理,并沒有注意到多線程的安全性的問題(在此編寫之前,我也存在這樣的經歷,不過還好。。。。),這往往造成編寫的程序在用戶量少的時候沒出什么問題,而一旦發現大量的并發用戶時,而且這數量達到一定的數量時,就會出現一系列莫名的問題,這問題在下面我們可以看的到。
    Servlet的多線程機制是怎么樣的呢:
    Servlet體系結構是建立在JAVA的多線程機制上的,但它的生命周期是由WEB容器來管理的,當客戶端第一次請求某個Servlet時,Servlet容器會根據web.xml的配置實例化相應的Servlet類,當有新的客戶端來請求這個Servlet時,容器一般不會再實例化這個Servlet類,而是以線程方式去調用這個實例的方法,然后再有更多的客戶端來請求時,就存在了多個線程在使用這個實例。并且Servlet容器會自動使用線程池技術來支持系統的運行。
    在這樣的情況,當兩個或者多個客戶端同時請求同一Serlvet時,就會存在多個線程同時訪問同一資源的情況,數據就可能變的不一致,所以在用Servlet搭建WEB應用程序時如果不考慮線程的問題,就會出現難以發現的問題。
    Servlet線程安全問題的例子:
    Servlet線程是由于使用實例變量不當而導致的,這里有如下的例子:
    代碼程序如下:
    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);
        }

    }

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

    大家看到這頁面什么數據也沒有,就是說那姓名沒有打印出來,那跑到哪里去了呢?看第二個用戶請求的情況。
    第二個用戶請求地址:http://127.0.0.1:8080/Test2/securityTest?name=b

    可以看到,原來a值已經打印到第二個用戶的瀏覽器了。
    可以想像在暫停5000時間里,第二個用戶請求這個servlet,已經把output的引用變成了第二個用戶請求的output值了,這樣就解釋了為什么會輸出到第二個客戶端的瀏覽的原因。

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

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

    可以看出,由于b線程對實例變量output的修改覆蓋了a線程對實例變量output的值,直接導致了用戶a的信息顯示到b的瀏覽器上。根據內存模型,我們也可以解釋到,正是因為b開始時修改了緩存中的output值,然后刷新到主內存中,而又有足夠的時間刷新到a緩存中,這時aOutput值就直接導致了指向b瀏覽器。
    解決方法:
    從上面的分析中,我們知道導致線程不安全的主要原因在于實例變量的使用不當,下面就提出如下三種解決方法
    第一,Servlet類實現SingleThreadModel接口,該接口指定系統如何處理對同一個Servlet的調用,如果一個Servlet被指定實現這個接口,那么,在這個Servlet中的service方法將不會在兩個線程中同時執行,也就是說執行完一個后再執行下一個請求的service,當然也就不存在線程不安全的問題了。
    代碼如下:
    public class SecurityTest extends HttpServlet implements SingleThreadModel{}
    其實這方法也就相當于是同步方法的效果吧。

    第二.同步對共享數據的操作。我們所熟悉的就是用synchronized關鍵字,這樣能保證一次只有一個線程來操作被保護的區段。在本例子也可以用synchronized來保證線程的安全,代碼如下:
    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);
    }
        }}
    第三:避免使用實例變量,而使用局部變量。因為Servlet線程不安全的原因是由實例變量引起,所以我們可以避免使用實例變量,而使用局部變量,線程之間很難直接訪問局部變量 ,這樣就從根本上解決了這一問題。
    在本例子中,就是將output放在service方法中當局部變量 。
    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);
        }}

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

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


    網站導航:
     
    主站蜘蛛池模板: 亚洲精品无码成人片在线观看| 妻子5免费完整高清电视| 日本免费v片一二三区| 一本色道久久综合亚洲精品蜜桃冫 | 中文字幕在线日亚洲9| 日韩精品无码区免费专区| 亚洲日本国产综合高清| a级毛片无码免费真人| 亚洲国产精品精华液| 国产一区二区三区在线观看免费 | 亚洲人成网站在线观看播放动漫 | 亚洲日本在线观看网址| 最近高清国语中文在线观看免费| 亚洲AV成人噜噜无码网站| 成年人在线免费观看| 无码色偷偷亚洲国内自拍| 亚洲美女在线国产| 无码日韩精品一区二区三区免费| 亚洲无线电影官网| 大学生一级毛片免费看| 亚洲AV无码片一区二区三区| 免费大黄网站在线观| 成人精品视频99在线观看免费| 午夜亚洲国产理论秋霞| 日韩精品福利片午夜免费观着| 男人和女人高潮免费网站| 亚洲国产精品成人精品无码区| 黄在线观看www免费看| 国产亚洲日韩在线a不卡| 亚洲男人的天堂www| 日本亚洲免费无线码| 国产成人亚洲综合a∨| 亚洲成AV人片天堂网无码| 国产男女爽爽爽爽爽免费视频| 亚洲av无一区二区三区| 亚洲国产精品高清久久久| 国产在线观看片a免费观看| 一级特级aaaa毛片免费观看| 亚洲成a人片在线观| 久久精品国产精品亚洲| 国产乱码免费卡1卡二卡3卡|