Race Condition: Singleton Member Field 競爭條件:單例的成員字段
Abstract
Servlet 成員字段可能允許一個用戶查看其他用戶的數據。
Explanation
許多 Servlet 開發人員都不了解 Servlet 為單例模式。 Servlet 只有一個實例,並通過使用和重復使用該單個實例來處理需要由不同線程同時處理的多個請求。 這種誤解的共同后果是,開發者使用 Servlet 成員字段的這種方式會導致某個用戶可能在無意中看到其他用戶的數據。 換言之,即把用戶數據存儲在 Servlet 成員字段中會引發數據訪問的 race condition。
例 1: 以下 Servlet 把請求參數值存儲在成員字段中,然后將參數值返回給響應輸出流。
public class GuestBook extends HttpServlet {
String name;
protected void doPost (HttpServletRequest req,
HttpServletResponse res) {
name = req.getParameter("name");
...
out.println(name + ", thanks for visiting!");
}
}
當該代碼在單一用戶環境中正常運行時,如果有兩個用戶幾乎同時訪問 Servlet,可能會導致這兩個請求以如下方式處理線程的插入:
線程 1: assign "Dick" to name
線程 2: assign "Jane" to name
線程 1: print "Jane, thanks for visiting!"
線程 2: print "Jane, thanks for visiting!"
因此會向第一個用戶顯示第二個用戶的用戶名。
Recommendation
不要為任何參數(常量除外)使用 Servlet 成員字段。 (例如,確保所有成員字段都是 static final)。當開發者需要把代碼內某一部分中的數據傳輸到另一部分時,他們經常使用 Servlet 成員字段存儲用戶數據。 如果您也是這么做的,可以考慮聲明一個單獨的類,並僅使用 Servlet “封裝”這個新類。
例 2: 上述例子中的 bug 可以利用以下方式進行修正:
public class GuestBook extends HttpServlet {
protected void doPost (HttpServletRequest req,
HttpServletResponse res) {
GBRequestHandler handler = new GBRequestHandler();
handler.handle(req, res);
}
}
public class GBRequestHandler {
String name;
public void handle(HttpServletRequest req,
HttpServletResponse res) {
name = req.getParameter("name");
...
out.println(name + ", thanks for visiting!");
}
}
此外, Servlet 也可以利用同步代碼塊來訪問 servlet 實例變量。但是,使用同步代碼塊可能會導致嚴重的性能問題。