Servlet基礎(三) Servlet的多線程同步問題
Servlet/JSP技術和ASP、PHP等相比,由於其多線程運行而具有很高的執行效率。
由於Servlet/JSP默認是以多線程模式執行的,所以,在編寫代碼時需要非常細致地考慮多線程的同步問題。
如果在編寫Servlet/JSP程序時不注意到多線程的同步問題,這往往造成程序在少量用戶訪問時沒有任何問題,而在並發用戶上升到一定值時,就會經常出現一些莫名其妙的問題,對於這類隨機性的問題調試難度也很大。
比如下面這個程序就有問題。
存在多線程問題的程序例子
這個例子中,首先有一個JSP頁面,其中有一個簡單的表單:
<form action="MultiThreadServlet"> <input type="text" name="username"> <input type="submit" value="submit"> </form>
提交表單后,轉向一個Servlet進行處理:
獲取請求中的參數,並且調用setAttribute方法將其值存儲,轉向下一個jsp頁面:
package com.shengqishiwind.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class MultiThreadServlet extends HttpServlet { //使用成員變量 private String username; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //從請求中得到參數,即用戶名 username = request.getParameter("username"); //得到當前線程的名字 System.out.println("Thread Name: " + Thread.currentThread().getName()); //模擬一些后端的業務處理 try { Thread.sleep(10000); } catch (Exception e) { e.printStackTrace(); } request.setAttribute("username", username); //請求轉發 request.getRequestDispatcher("hello.jsp").forward(request, response); } }
中間讓線程停留了10秒鍾,來模擬一些操作。
在下一個JSP頁面中將該值顯示出來:
<body> username: <%= request.getAttribute("username")%> </body>
這樣做有什么問題呢?
打開瀏覽器,輸入訪問地址后,輸入一個用戶名zhangsan,再打開一個窗口,輸入用戶名lisi。
兩個瀏覽器窗口都提交以后,過了一定時間,可以看到兩邊返回值都是lisi。
問題原因
Servlet的多線程同步問題:
Servlet本身是單實例的,這樣當有多個用戶同時訪問某個Servlet時,會訪問該唯一的Servlet實例中的成員變量,如果對成員變量進行寫入操作,那就會導致Servlet的多線程問題,即數據不一致。
解決同步問題的方案
1.解決Servlet多線程同步問題的最好方式:
去除實例變量,使用局部變量。
比如上面那個例子修改如下:
public class MultiThreadServlet extends HttpServlet { //使用成員變量 //private String username; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //從請求中得到參數,即用戶名 String username = request.getParameter("username"); //得到當前線程的名字 System.out.println("Thread Name: " + Thread.currentThread().getName()); //模擬一些后端的業務處理 try { Thread.sleep(10000); } catch (Exception e) { e.printStackTrace(); } request.setAttribute("username", username); //請求轉發 request.getRequestDispatcher("hello.jsp").forward(request, response); } }
不使用成員變量,而使用局部變量,因為局部變量在每個線程中都有各自的實例。
所以對Servlet來說,如果要對某個變量做寫入操作,一定不要使用成員變量,而要使用局部變量。
2.使用同步代碼塊
synchronized{}
3.Servlet實現javax.serlvet.SingleThreadModel(Servlet2.4中已經廢棄了該接口),此時Servlet容器將保證Servlet實例以單線程方式運行,也就是說,同一時刻,只會有一個線程執行Servlet的service()方法。
(這種方式了解一下就行了)。
參考資料
聖思園張龍老師Java Web視頻教程。