Servlet 容器採用「單實例、多線程」模型處理請求,這雖然極大提升了效能,但也引入了線程安全隱患。本文將從容器模型、變量與屬性安全、已廢棄的 SingleThreadModel 到實戰總結,系統梳理 Servlet 線程安全的核心知識。
預設情況下,Servlet 容器對每個聲明的 Servlet 只創建一個實例。當多個客戶端並發請求同一 Servlet 時,容器從線程池調度多條線程並發執行該實例的 service()(或 doGet/doPost)方法。這意味著實例變量、靜態變量、共享作用域屬性均可能被多線程同時訪問修改,從而產生線程安全問題。
實例變量屬於 Servlet 實例,所有線程共享。若線程 A 讀取參數賦值給實例變量,尚未使用時時間片用盡,線程 B 覆蓋該變量,線程 A 恢復執行時將使用線程 B 的數據,導致業務邏輯錯誤。
synchronized,但會嚴重降低併發性能,不推薦用於整個 doGet。final 修飾(如資料庫連接字串)。全應用共享,任何 Servlet、線程均可讀寫。對其中的集合屬性進行「讀取大小→遍歷」或「檢查後修改」等非原子操作極易拋出 IndexOutOfBoundsException。解決:拷貝副本或同步鎖定 ServletContext 對象。
理論上屬於單用戶,但同一瀏覽器多標籤頁共享 Session,且容器多線程處理請求,同樣會發生併發修改集合導致的索引越界。解決:同步鎖定 session 對象(synchronized(session))。
每次請求創建新對象,屬於線程私有,天然安全。
實現此接口強制容器對該 Servlet 實例單線程執行,或創建多實例。但無法解決 ServletContext、HttpSession 共享數據的併發問題,且嚴重損耗效能,已在 Servlet 2.4 中廢棄。
final。synchronized(session)),縮小鎖範圍。