轉自: https://www.cnblogs.com/gw811/archive/2012/09/07/2674859.htmlhtml
Servlet/JSP技術和ASP、PHP等相比,因爲其多線程運行而具備很高的執行效率。因爲Servlet/JSP默認是以多線程模式執行的,所 以,在編寫代碼時須要很是細緻地考慮多線程的安全性問題。然而,不少人編寫Servlet/JSP程序時並無注意到多線程安全性的問題,這每每形成編寫 的程序在少許用戶訪問時沒有任何問題,而在併發用戶上升到必定值時,就會常常出現一些莫明其妙的問題。web
當多個客戶端併發訪問同一個Servlet時,web服務器會爲每個客戶端的訪問請求建立一個線程,並在這個線程上調用Servlet的service方法,所以service方法內若是訪問了同一個資源的話,就有可能引起線程安全問題。瀏覽器
一、若是某個Servlet實現了SingleThreadModel接口,那麼Servlet引擎將以單線程模式來調用其service方法。可是本質上面並無解決線程安全問題,由於其採用的方式是根據併發請求去產生多個Servlet實例對象,併發的每一個線程分別調用一個獨立的Servlet實例對象,而真正意義上解決多線程安全問題是指一個Servlet實例對象被多個線程同時調用的問題。而不是多個線程調用多個Servlet實例。
二、利用synchronized同步代碼,將咱們有併發問題的代碼轉化爲同步代碼,可是這樣作的一個壞處就是,若是遇到高併發的狀況下,咱們的用戶可能會由於同步代碼的問題而形成等待的時間比較久這樣的問題,因此,要慎用。
三、減小引發併發問題的資源代碼,從根本上解決Servlet線程安全問題,其實,形成Servlet線程的問題好多都是由於變量設置不正確形成的,咱們應該儘量去避免這樣的問題。緩存
總結1:servlet是線程不安全的,而形成這種緣由主要是覺得實例變量不正確的使用。
總結2 :儘可能不使用synchronized來解決Servlet線程安全問題安全
Servlet運行過程:
Servlet程序是由WEB服務器調用,web服務器收到客戶端的Servlet訪問請求後:
① Web服務器首先檢查是否已經裝載並建立了該Servlet的實例對象。若是是,則直接執行第④步,不然,執行第②步。
② 裝載並建立該Servlet的一個實例對象。
③ 調用Servlet實例對象的init()方法。
④ 建立一個用於封裝HTTP請求消息的HttpServletRequest對象和一個表明HTTP響應消息的HttpServletResponse對象,而後調用Servlet的service()方法並將請求和響應對象做爲參數傳遞進去。
⑤WEB應用程序被中止或從新啓動以前,Servlet引擎將卸載Servlet,並在卸載以前調用Servlet的destroy()方法。服務器
經過第二步,咱們能夠知道,建立的Servlet對象只有一個。多線程
Servlet體系結構是創建在Java多線程機制之上的,它的生命週期是由Web容器負責的。當客戶端第一次請求某個Servlet時,Servlet容器將會根據web.xml配置文件實例化這個Servlet類。當有新的客戶端請求該Servlet時,通常不會再實例化該Servlet類,也就是有多個線程在使用這個實例。Servlet容器會自動使用線程池等技術來支持系統的運行,如圖1所示。併發
這樣,當兩個或多個線程同時訪問同一個Servlet時,可能會發生多個線程同時訪問同一資源的狀況,數據可能會變得不一致。因此在用Servlet構建的Web應用時若是不注意線程安全的問題,會使所寫的Servlet程序有難以發現的錯誤。高併發
———————————————— 華麗的分隔符 —————————————————性能
Servlet的線程安全問題主要是因爲實例變量使用不當而引發的,這裏以一個現實的例子來講明。
該Servlet中定義了一個實例變量output,在service方法將其賦值爲用戶的輸出。當一個用戶訪問該Servlet時,程序會正常的運行,但當多個用戶併發訪問時,就可能會出現其它用戶的信息顯示在另一些用戶的瀏覽器上的問題。這是一個嚴重的問題。爲了突出併發問題,便於測試、觀察,咱們在回顯用戶信息時執行了一個延時的操做。假設已在web.xml配置文件中註冊了該Servlet,現有兩個用戶a和b同時訪問該Servlet(能夠啓動兩個IE瀏覽器,或者在兩臺機器上同時訪問),即同時在瀏覽器中輸入:
a: http://localhost:8080/ServletTest/ConcurrentTest?Username=a
b: http://localhost:8080/ServletTest/ConcurrentTest?Username=b
若是用戶b比用戶a回車的時間稍慢一點,將獲得如圖2所示的輸出:
從圖2中能夠看到,Web服務器啓動了兩個線程分別處理來自用戶a和用戶b的請求,可是在用戶a的瀏覽器上卻獲得一個空白的屏幕,用戶a的信息顯示在用戶b的瀏覽器上。該Servlet存在線程不安全問題。下面咱們就從分析該實例的內存模型入手,觀察不一樣時刻實例變量output的值來分析使該Servlet線程不安全的緣由。
在Java的內存模型JMM中咱們講過,JMM主要是爲了規定了線程和內存之間的一些關係。根據JMM的設計,系統存在一個主內存(Main Memory),Java中全部實例變量都儲存在主存中,對於全部線程都是共享的。每條線程都有本身的工做內存(Working Memory),工做內存由緩存和堆棧兩部分組成,緩存中保存的是主存中變量的拷貝,緩存可能並不總和主存同步,也就是緩存中變量的修改可能沒有馬上寫到主存中;堆棧中保存的是線程的局部變量,線程之間沒法相互直接訪問堆棧中的變量。根據JMM,咱們能夠將論文中所討論的Servlet實例的內存模型抽象爲圖3所示的模型。
從圖4中能夠清楚的看到,因爲b線程對實例變量output的修改覆蓋了a線程對實例變量output的修改,從而致使了用戶a的信息顯示在了用戶b的瀏覽器上。若是在a線程執行輸出語句時,b線程對output的修改尚未刷新到主存,那麼將不會出現圖2所示的輸出結果,所以這只是一種偶然現象,但這更增長了程序潛在的危險性。
經過上面的分析,咱們知道了實例變量不正確的使用是形成Servlet線程不安全的主要緣由。下面針對該問題給出了三種解決方案並對方案的選取給出了一些參考性的建議。
一、實現 SingleThreadModel 接口 ( 過期 )
該接口指定了系統如何處理對同一個Servlet的調用。若是一個Servlet被這個接口指定,那麼在這個Servlet中的service方法將不會有兩個線程被同時執行,固然也就不存在線程安全的問題。這種方法只要將前面的Concurrent Test類的類頭定義更改成:
注意:SingleThreadModel不會解決全部的線程安全隱患。 例如,會話屬性和靜態變量仍然能夠被多線程的多請求同時訪問,即使使用了SingleThreadModel servlet。建議開發人員應當採起其餘手段來解決這些問題,而不是實現該接口,好比 避免實例變量的使用或者在訪問資源時同步代碼塊。該接口在Servlet API 2.4中將不推薦使用。
二、同步對共享數據的操做 ( 慎用 )
使用synchronized 關鍵字能保證一次只有一個線程能夠訪問被保護的區段,在本論文中的Servlet能夠經過同步塊操做來保證線程的安全。同步後的代碼以下:
三、避免使用實例變量( 推薦 )
本實例中的線程安全問題是由實例變量形成的,只要在Servlet裏面的任何方法裏面都不使用實例變量,那麼該Servlet就是線程安全的。
修正上面的Servlet代碼,將實例變量改成局部變量實現一樣的功能,代碼以下:
對上面的三種方法進行測試,能夠代表用它們都能設計出線程安全的Servlet程序。可是,若是一個Servlet實現了SingleThreadModel接口,Servlet引擎將爲每一個新的請求建立一個單獨的Servlet實例,這將引發大量的系統開銷。SingleThreadModel在Servlet2.4中已再也不提倡使用;一樣若是在程序中使用同步來保護要使用的共享的數據,也會使系統的性能大大降低。這是由於被同步的代碼塊在同一時刻只能有一個線程執行它,使得其同時處理客戶請求的吞吐量下降,並且不少客戶處於阻塞狀態。另外爲保證主存內容和線程的工做內存中的數據的一致性,要頻繁地刷新緩存,這也會大大地影響系統的性能。因此在實際的開發中也應避免或最小化 Servlet 中的同步代碼;在Serlet中避免使用實例變量是保證Servlet線程安全的最佳選擇。從Java 內存模型也能夠知道,方法中的臨時變量是在棧上分配空間,並且每一個線程都有本身私有的棧空間,因此它們不會影響線程的安全。