本系列從零構建網頁訪問計數器,逐步揭示併發安全、持久化、架構分層等核心課題。首篇展示修正版,使用 synchronized 鎖解決 application 域物件的多線程競態條件。
計數器邏輯:從 application 取出計數、+1、存回。多用戶並發訪問時,若無同步,會出現「讀取到相同舊值 → 各自+1 → 寫回」導致計數遺漏。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>計數器修正版</title></head><body>
<%!
// 類級別鎖,全 Web 應用共享
private static final Object lock = new Object();
%>
Hello World!
<%
Integer count = 0;
synchronized (lock) { // 關鍵:同步塊保護「讀-改-寫」原子性
// 從 application 域取得計數器
count = (Integer) application.getAttribute("INDEX_COUNTER");
// 首次訪問初始化
if (count == null) {
count = 0;
application.setAttribute("INDEX_COUNTER", count);
}
count++;
application.setAttribute("INDEX_COUNTER", count);
}
%>
當前總訪問次數:<%= count %>
</body></html>
Integer 而非 int:application.getAttribute 返回 Object,首次訪問返回 null,若強轉 int 會拋 NullPointerException;用 Integer 可判空再自動拆箱。jspInit 隱含物件(<%! %>):確保整個 Web 應用只有一把鎖,所有請求線程競爭同一把鎖。count = ... 後加 Thread.sleep(10000),雙瀏覽器同時刷新,無鎖版計數只增 1,有鎖版正確增 2。application 隨 ServletContext 銷毀而消失,Tomcat 重啟計數歸零。下一篇將創建標準 Dynamic Web Project,並改用檔案持久化解決重啟歸零問題。