1.?避免共享狀態(最佳實踐)
-
核心思想:Servlet 本身應設計為無狀態(Stateless),不依賴實例變量存儲請求相關數據。
-
實現方式:
-
將變量聲明在方法內部(局部變量),每個線程獨享棧內存。
-
若需跨請求傳遞數據,使用請求作用域(
HttpServletRequest
)或會話作用域(HttpSession
)。
-
-
示例:
public class SafeServlet extends HttpServlet {// ? 危險:實例變量被所有線程共享// private int counter;protected void doGet(HttpServletRequest req, HttpServletResponse resp) {// ? 安全:局部變量,線程獨享int localCounter = 0;localCounter++;resp.getWriter().write("Count: " + localCounter);} }
2.?使用線程安全的數據結構
-
適用場景:必須共享資源時(如全局計數器、緩存)。
-
實現方式:
-
使用?
java.util.concurrent
?包中的線程安全類:
ConcurrentHashMap
,?AtomicInteger
,?CopyOnWriteArrayList
?等。 -
避免直接使用非線程安全的類(如?
HashMap
、ArrayList
)。
-
-
示例:
public class CounterServlet extends HttpServlet {// ? 線程安全計數器private AtomicInteger atomicCounter = new AtomicInteger(0);protected void doGet(HttpServletRequest req, HttpServletResponse resp) {int count = atomicCounter.incrementAndGet();resp.getWriter().write("Atomic Count: " + count);} }
3.?同步(Synchronization)
-
適用場景:需保護臨界區(Critical Section)代碼時。
-
實現方式:
-
使用?
synchronized
?關鍵字修飾方法或代碼塊。 -
注意鎖的粒度:盡量縮小同步范圍以提高性能。
-
-
示例:
public class SyncServlet extends HttpServlet {private int counter = 0;private final Object lock = new Object(); // 專用鎖對象protected void doGet(HttpServletRequest req, HttpServletResponse resp) {synchronized (lock) { // ? 同步代碼塊counter++;resp.getWriter().write("Sync Count: " + counter);}} }
4.?使用 ThreadLocal
-
適用場景:需要為每個線程維護獨立副本的資源(如數據庫連接、SimpleDateFormat)。
-
原理:通過?
ThreadLocal
?為每個線程創建資源副本,避免競爭。 -
示例:
public class DateFormatServlet extends HttpServlet {// ? 每個線程獨立持有 SimpleDateFormatprivate static final ThreadLocal<SimpleDateFormat> dateFormat =ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));protected void doGet(HttpServletRequest req, HttpServletResponse resp) {SimpleDateFormat sdf = dateFormat.get(); // 獲取當前線程的副本String date = sdf.format(new Date());resp.getWriter().write(date);}@Overridepublic void destroy() {dateFormat.remove(); // 清理線程副本} }
5.?外部化資源管理
-
適用場景:數據庫連接池、緩存等需線程安全的外部資源。
-
實現方式:
-
使用成熟的線程安全中間件(如 Redis、數據庫連接池 HikariCP)。
-
確保資源本身是線程安全的(如 JDBC 的?
DataSource
)。
-
6.?Servlet 作用域控制(謹慎使用)
-
通過配置使 Servlet 非單例(僅特定容器支持,如通過?
@WebServlet(urlPatterns="...", loadOnStartup=1, asyncSupported=true)
?配置異步模式)。 -
替代方案:使用框架(如 Spring MVC 的?
@Scope("prototype")
),但需權衡性能。
關鍵原則總結
策略 | 適用場景 | 優點 | 缺點 |
---|---|---|---|
無狀態設計 | 絕大多數情況 | 簡單高效,無需同步 | 不適合必須共享資源的場景 |
線程安全類 | 共享計數器、緩存等 | 性能高,無需手動同步 | 功能受限 |
同步(synchronized) | 臨界區操作(如文件寫入) | 靈活,可控粒度 | 性能下降,可能死鎖 |
ThreadLocal | 線程綁定資源(如數據庫連接) | 避免競爭,資源隔離 | 內存泄漏風險 |
常見陷阱與解決方案
-
SimpleDateFormat 非線程安全
-
? 錯誤做法:
private SimpleDateFormat sdf = new SimpleDateFormat(...);
-
? 正確做法:使用?
ThreadLocal
?或替換為?DateTimeFormatter
(Java 8+ 線程安全)。
-
-
Servlet 中存儲用戶狀態
-
? 錯誤做法:在 Servlet 實例變量中保存用戶數據。
-
? 正確做法:使用?
HttpSession
?或請求參數。
-
-
過度同步導致性能瓶頸
-
? 錯誤做法:
synchronized
?修飾整個?service()
?方法。 -
? 正確做法:縮小同步范圍至必要代碼塊。
-
通過合理選擇上述策略,可以在保證線程安全的前提下,最大限度提升 Servlet 的并發性能