Servlet與線程安全
先說結論:servlet不是線程安全的。
servlet運行過程
Servlet程序是由WEB服務器調用,web服務器收到客戶端的Servlet訪問請求後:
- ①Web服務器首先檢查是否已經裝載並創建了該Servlet的實例對象。如果是,則直接執行第④步,否則,執行第②步。
- ②裝載並創建該Servlet的一個實例對象。
- ③調用Servlet實例對象的init()方法。
- ④創建一個用於封裝HTTP請求消息的HttpServletRequest對象和一個代表HTTP響應消息的HttpServletResponse對象,然後調用Servlet的service()方法並將請求和響應對象作為參數傳遞進去。
- ⑤WEB應用程序被停止或重新啟動之前,Servlet引擎將卸載Servlet,並在卸載之前調用Servlet的destroy()方法。
servlet容器如何同時處理多個請求
Servlet采用多線程來處理多個請求同時訪問,Servelet容器維護了一個線程池來服務請求。線程池實際上是等待執行代碼的一組線程叫做工作者線程(Worker Thread),Servlet容器使用一個調度線程來管理工作者線程(Dispatcher Thread)。
當容器收到一個訪問Servlet的請求,調度者線程從線程池中選出一個工作者線程,將請求傳遞給該線程,然後由該線程來執行Servlet實例的service方法。當這個線程正在執行的時候,容器收到另外一個請求,調度者線程將從池中選出另外一個工作者線程來服務新的請求,容器並不關系這個請求是否訪問的是同一個Servlet實例還是另外一個Servlet實例。
當容器同時收到對同一Servlet實例的多個請求,那這個Servlet實例的service方法將在多線程中並發的執行。
為什麽servlet不是線程安全的
Servlet以單例多線程方式運行,也就是說當有多個請求訪問同一個Servlet的方法時,只會創建一個Servlet對象。
如果Servlet方法中使用了非局部變量,就會產生線程安全問題。
方法中的局部變量全部是在棧幀的局部變量表中,棧幀是棧的一部分,而棧是線程私有的,因此Servlet中的局部變量不會引起線程安全問題。局部變量存在於棧中,如果是引用類型,那麽它指向堆中的對象。
Servlet相關成員的線程安全
ServletContext:非線程安全。ServletContext是可以多線程同時讀/寫屬性的,因此是非線程安全的。要對屬性的讀寫進行同步處理或者進行深度Clone()。所以在Servlet上下文中盡可能少量保存會被修改(寫)的數據,可以采取其他方式在多個Servlet中共享,比方我們可以使用單例模式來處理共享數據。
HttpSession:非線程安全。HttpSession對象在用戶會話期間存在,只能在處理屬於同一個Session的請求的線程中被訪問,因此Session對象的屬性訪問理論上是線程安全的。但當用戶打開多個同屬於一個進程的瀏覽器窗口,在這些窗口的訪問屬於同一個Session,會出現多次請求,需要多個工作線程來處理請求,可能造成同時多線程讀寫屬性。這時我們需要對屬性的讀寫進行同步處理:使用同步塊Synchronized和使用讀/寫器來解決。
ServletRequest:線程安全。對於每一個請求,由一個工作線程來執行,都會創建有一個新的ServletRequest對象,所以ServletRequest對象只能在一個線程中被訪問,因此ServletRequest是線程安全的。註意:ServletRequest對象在service方法的範圍內是有效的,不要試圖在service方法結束後仍然保存請求對象的引用。
如何安全地使用Servlet
盡量使用局部變量。多線程並不共享局部變量,所以我們要盡可能的在servlet中使用局部變量。
對需要異步調用的代碼塊加鎖,不過這意味著線程需要排隊處理,因為在使用同板塊的時候要盡可能的縮小同步代碼的範圍,不要直接在sevice方法和響應方法上使用同步,這樣會嚴重影響性能。
使用線程安全的類:如使用Vector代替ArrayList,使用Hashtable代替HashMap。
不要在Servlet中創建自己的線程來完成某個功能。Servlet本身就是多線程的,在Servlet中再創建線程,將導致執行情況復雜化,出現多線程安全問題。
在多個servlet中對外部對象(比方文件)進行修改操作一定要加鎖,做到互斥的訪問。
#########
Servlet與線程安全