上一篇介紹了Servlet初始化,以及如何處理HTTP請求,實際上在這兩個過程中,都伴隨著Servlet的生命周期,都是Servlet生命周期的一部分。同時,由於Tomcat容器預設是採用單實例多線程的方式處理多個請求,這一特性就導致了線程安全問題的存在。因此,本篇主要講述Servlet生命周期與線 ...
上一篇介紹了Servlet初始化,以及如何處理HTTP請求,實際上在這兩個過程中,都伴隨著Servlet的生命周期,都是Servlet生命周期的一部分。同時,由於Tomcat容器預設是採用單實例多線程的方式處理多個請求,這一特性就導致了線程安全問題的存在。因此,本篇主要講述Servlet生命周期與線程安全問題。
1、Servlet生命周期
Servlet是運行在容器當中的,所以其生命周期也由容器控制,最常用的容器就是Tomcat,筆者經歷過的所有項目也都是以Tomcat作為Servlet的容器。經過前面幾篇介紹,相信大家對Servlet的生命周期有了一定的瞭解。Servlet的生命周期其實是通過javax.servlet.Servlet介面中的init()、service()、destroy()等方法表示的,主要有四個階段組成:載入並實例化、初始化(init())、處理請求(service())、銷毀(destroy())。下麵分別介紹這四個階段。
載入並實例化
Tomcat容器負責Servlet的載入並實例化,其實例化分兩種情況:當web.xml文件里配置了<load-on-startup>標簽並且裡面的數字>=0時,容器啟動時即載入Servlet類並創建類的實例;如果未配置<load-on-startup>標簽或數字<0時,容器啟動時不會載入Servlet類,當然也就不會創建類的實例。這時,當用戶首次訪問Servlet類時會載入並實例化。無論採用哪種方式實例化,都只會創建一個類的實例,無論多少用戶訪問Servlet,都共用這一個實例。
初始化(init())
在Servlet類實例化之後,容器將調用init()方法,傳遞ServletConfig介面的對象,進行初始化。在init()方法中,可以通過getServletConfig()方法獲取ServletConfig對象,然後通過此對象的getInitParameter()等方法獲取web.xml文件中<init-param>標簽裡面的配置信息,並對配置信息進行解析,或者執行任何其他一次性活動。在Servlet的整個生命周期中,init()方法只會被執行一次。
處理請求(service())
在Servlet初始化完成之後,容器就準備接收並處理客戶的請求了。處理請求時,容器會調用Servlet的service(HttpServletRequest req, HttpServletResponse resp)方法,這個方法會判斷用戶發送的請求類型,是“POST”請求還是“GET”請求或是其他請求,然後根據請求類型執行相應的doPost()方法、doGet()方法或其他方法。Tomcat容器會將用戶請求的數據封裝到HttpServletRequest對象中,伺服器處理完用戶請求之後,將結果信息返回到HttpServletResponse對象中,最終這兩個對象作為參數傳遞到doPost()、doGet()或其他方法中,將結果信息返回到頁面顯示。當多個客戶的請求到來時,伺服器會創建多個線程,每個客戶請求對應一個線程,每個請求的service()方法都能運行在自己獨立的線程中。
銷毀(destroy())
當Tomcat容器關閉時或由於其他原因導致Servlet需要關閉或卸載時,容器會調用該對象的destroy()方法,以便讓Servlet對象可以釋放它所使用的資源,該方法同樣只會執行一次。在容器調用destroy()方法前,如果還有其他的線程正在service()方法中執行,容器會等待這些線程執行完畢或者等待伺服器設定的超時值到達。一旦Servlet對象的destroy()方法被調用,容器會釋放這個Servlet對象,在隨後的時間內,該對象會被java的垃圾收集器所回收。這四個階段共同組成了Servlet的生命周期。
2、Servlet線程安全
通過上面的Servlet生命周期可以看出,在Tomcat容器載入並實例化Servlet之後,會創建一個實例,並且這個實例是唯一的,無論多少用戶訪問Servlet,都共用這一個實例。而每次用戶訪問Servlet時,伺服器都會為每個用戶創建一個獨立的線程,每個線程都有它自己的堆棧空間。所以說是單實例多線程,這種預設以多線程方式執行的設計可大大降低對系統的資源需求,提高系統的併發量及響應時間,但也同時引發了Servlet的線程安全問題。
對於Servlet中的局部變數,多線程下每個線程對局部變數都會有自己的一份copy,存在自己的堆棧空間中,這樣對局部變數的修改只會影響到自己的copy而不會對別的線程產生影響,所以這是線程安全的;對於Servlet中的實例(全局)變數,多線程下所有線程共用實例變數,這一共用就可能導致多個線程之間互相影響,從而引發線程的不安全。
知道了引發線程不安全問題的原因,那麼該如何預防這一情況發生呢?
不使用實例變數
既然實例變數能引發線程安全問題,那麼只要在Servlet類的任何方法裡面都不使用實例變數,該Servlet就是線程安全的。事實上,線程安全問題大部分是由實例變數造成的,在Servlet中避免使用實例變數是保證Servlet線程安全的最佳選擇。
使用synchronized
synchronized關鍵字能保證一次只有一個線程可以訪問被保護的區段,所以理論上可以通過同步塊操作來保證Servlet的線程安全。但因為其“一次只有一個線程可以訪問”的特性,導致當大量用戶訪問同一資源時,只能排隊訪問,大量用戶處於阻塞狀態,這就大大降低了其用戶的吞吐量,從而使系統的效率和性能大大降低,不推薦使用此方法。
其他方式
Java的有些集合類也會引發線程安全問題,應避免使用。比如用Vector代替ArrayList,用Hashtable代替HashMap等。另外,不要在Servlet中創建其他線程來完成某個功能,因為Servlet本身就是多線程的,再在Servlet中創建線程,更容易引發線程安全問題。
轉載請註明出處 http://www.cnblogs.com/Y-oung/p/8433426.html
工作、學習、交流或有任何疑問,請聯繫郵箱:[email protected] 微信:yy1340128046