JSP預設採用多線程執行模型,這是其區別於ASP、PHP、Perl等腳本語言的關鍵特性,也是高併發性能的基石。然而,若忽視多線程環境下的同步問題,極易引入難以復現的隱蔽缺陷。本文通過具體案例深入剖析JSP多線程風險及解決之道。
當客戶端首次請求某JSP時,容器將其編譯為Servlet類並創建實例,後續請求以多線程方式共享該實例。變量的線程安全性取決於其存儲位置:
out、request、response、session、config、page、pageContext均為線程安全;application跨全局共享,非線程安全。<%@ page import="javax.naming.*, java.util.*, java.sql.*, weblogic.common.*" %>
<%
String name = request.getParameter("name");
String product = request.getParameter("product");
long quantity = Long.parseLong(request.getParameter("quantity")); /*(1)*/
savebuy();
%>
<%!
public void savebuy() {
try {
Properties props = new Properties();
props.put("user", "scott");
props.put("password", "tiger");
props.put("server", "DEMO");
Driver myDriver = (Driver) Class.forName("weblogic.jdbc.oci.Driver").newInstance();
Connection conn = myDriver.connect("jdbc:weblogic:oracle", props);
Statement stmt = conn.createStatement();
String inssql = "insert into buy(empid, name, dept) values (?, ?, ?)";
PreparedStatement pstmt = conn.prepareStatement(inssql);
pstmt.setString(1, name);
pstmt.setString(2, product);
pstmt.setLong(3, quantity);
pstmt.execute();
} catch (Exception e) {
System.out.println("SQLException: " + e.getMessage());
} finally {
try { if (stmt != null) stmt.close(); } catch (SQLException sqle) {}
try { if (conn != null) conn.close(); } catch (SQLException sqle) {}
}
}
%>
上述代碼模擬購物下單場景,name、product、quantity聲明為實例變量(在<%! %>聲明中),導致多線程並發時數據競爭。具體風險:線程A讀取參數賦值給quantity時被調度掛起,線程B修改了quantity,線程A恢復執行時將保存線程B的值,造成訂單數據錯誤——這在電商系統中屬於嚴重業務缺陷。
_jspService方法棧幀中處理,或封裝到請求作用域的JavaBean中。synchronized鎖,但會降低併發性能。ConcurrentHashMap替代HashMap。核心原則:請求處理過程中產生的業務數據,嚴禁存儲於實例或靜態變量中。這是構建高可靠JSP應用的基石。