本文來自stackoverflow的問答,討論了Java Servlet的工做機制,如何進行實例化、共享變量和多線程處理。html
假設我有一個運行了大量 Servlet
的 web 服務器。經過 Servlet
之間傳輸信息獲得 Servlet
上下文,並設置 session 變量。java
如今,若是有兩名或更多使用者向這個服務發送請求,接下來 session 變量會發生什麼變化?到底是全部用戶都是用共同的變量?仍是不一樣的用戶使用的變量都不同?若是是後者,服務器如何區分不一樣用戶?git
另外一個類似的問題,若是有 *n*
名用戶訪問一個特定的 Servlet
,那麼該 Servlet
是僅在第一個用戶首次訪問的時候實例化,仍是分別爲每一個用戶實例化?github
當 Servlet 容器(好比 Apache Tomcat)啓動後,會部署和加載全部 web 應用。當web 應用被加載,Servlet 容器會建立一次 ServletContext
,而後將其保存在服務器的內存中。web 應用的 web.xml
被解析,找到其中全部 servlet
、filter
和 Listener
或 @WebServlet
、@WebFilter
和 @WebListener
註解的內容,建立一次並保存到服務器的內存中。對於全部過濾器會當即調用 init()
。當 Servlet 容器中止,將卸載全部 web 應用,調用全部初始化的 Servlet 和過濾器的 destroy()
方法,最後回收 ServletContext
和全部 Servlet
、Filter 與 Listener
實例。web
當問題中的 Servlet
配置的 load-on-startup
或者 @WebServlet(loadOnStartup)
設置了一個大於 0 的值,則一樣會在啓動的時候當即調用 init()
方法。「load-on-startup」中的值表示那些 Servlet 會以相同順序初始化。若是配置的值相同,會遵循 web.xml
中指定的順序或@WebServlet
類加載的順序。另外,若是不設置 「load-on-startup」 值,init()
方法只在第一次 HTTP 請求命中問題中的 Servlet 時才被調用。apache
Servlet 容器附加在一個 web 服務上,這個 web 服務會在某個端口號上監聽 HTTP 請求,在開發環境中這個端口一般爲 8080,生產環境中一般爲 80。當客戶端(web 瀏覽器)發送了一個 HTTP 請求,Servlet 容器會建立新的 HttpServletRequest
和 HttpServletResponse
對象,傳遞給已建立好而且請求的 URL 匹配 url-pattern
的 Filter
和 Servlet
實例中的方法,全部工做都在同一個線程中處理。api
request 對象能夠訪問全部該 HTTP 請求中的信息,例如 request header 和 request body。response 對象爲你提供須要的控制和發送 HTTP 響應方法,例如設置 header 和 body(一般會帶有 JSP 文件中的 HTML 內容)。提交併完成HTTP 響應後,將回收 request 和 response 對象。瀏覽器
當用戶第一次訪問該 web 應用時,會經過 request.getSession()
第一次得到 HttpSession。以後 Servlet 容器將會建立 HttpSession
,生成一個惟一的 ID(能夠經過 session.getId()
獲取)並儲存在服務器內存中。而後 Servlet 容器在該次 HTTP 響應的 Set-Cookie
頭部設置一個Cookie
,以 JSESSIONID
做爲 Cookie 名字,那個惟一的 session ID 做爲 Cookie
的值。tomcat
按照 HTTP cookie 規則(正常 web 瀏覽器和 web 服務端必須遵循的標準),當 cookie 有效時,要求客戶端(瀏覽器)在後續請求的 Cookie
頭中返回這個 cookie。使用瀏覽器內置的 HTTP 流量監控器,你能夠查看它們(在 Chrome、Firefox23+、IE9+ 中按 F12,而後查看 Net/Network 標籤)。Servlet 容器將會肯定每一個進入的 HTTP 請求的 Cookie
頭中是否存在名爲JSESSIONID
的 cookie,而後用它的值(session ID)從服務端內存中找到關聯的 HttpSession
。安全
你能夠在 web.xml
中設置 session-timeout
,默認值爲 30 分鐘。超時到達以前 HttpSession
會一直存活。因此當客戶端再也不訪問該 web 應用超過 30 分鐘後,Servlet 容器就會回收這個 session。後續每一個請求,即便指定 cookie 名稱也不能再訪問到相同的 session。Servlet 容器會建立一個新的 Cookie
。
另外一方面,客戶端上的 session cookie 有一個默認存活時間,該事件和該瀏覽器實例運行時間同樣長。因此,當客戶端關閉該瀏覽器實例(全部標籤和窗口)後,這個 session 就會被客戶端回收。新瀏覽器實例再也不發送與該 session 關聯的 cookie。一個新的 request.getSession()
將會返回新的 HttpSession
並設置一個擁有新 session
ID 的 cookie。
ServletContext
與 web 應用存活時間同樣長。它被全部 session 中的全部請求共享。HttpServletRequest
和 HttpServletResponse
的存活時間爲客戶端發送完成到完整的響應(web 頁面)到達的這段時間。不會被其餘地方共享。全部 Servlet
、Filter
和 Listener
對象在 web 應用運行時都是活躍的。它們被全部 session 中的請求共享。HttpServletRequest
、HttpServletResponse
和 HttpSession
中的全部屬性在問題中的對象存活時都會一直保持存活。即使如此,你最關心的多是線程安全。你如今應該學習到 Servlet 和 filter 被全部請求共享。那是 Java 的一個優勢,使得多個不一樣線程(讀取 HTTP 請求)可使用同一個實例。不然爲每一個請求從新建立線程的開銷實在過於昂貴。
但你應該也意識到永遠不要將任何 request 或 session 域中的數據賦值給 servlet 或 filter 的實例變量。它將會被全部其餘 session 中的全部請求共享。那是非線程安全的!下面的示例對這種狀況進行了展現:
public class ExampleServlet extends HttpServlet { private Object thisIsNOTThreadSafe; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Object thisIsThreadSafe; thisIsNOTThreadSafe = request.setParameter("foo"); // BAD!! Shared among all requests! thisIsThreadSafe = request.getParameter("foo"); // OK, this is thread safe. } }
原文連接: stackoverflow
首發至: http://www.importnew.com/17025.html,並已同步至 Github,歡迎 Star 關注。