servlet是何物?
1,http://www.ibm.com/developerworks/cn/java/j-lo-servlet/
由于 servlet 是单例的,所以所有对这个 servlet 的请求都是共享一个实例 因此导致成员变量 username 对于所有请求这个 servlet 的请求都是共享了,在多线程情况下就回导致 username 的值冲突,结果请看演示;用两个不同的浏览器(模拟两个线程)分别请求这个 servlet,并且输入不同的值,跳转之后两个show.jsp显示的值是后面点击提交的值;下面代码演示这个问题;
package com.ubuntuvim.servlet.concurrent;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author ubuntuvim
* @Email cdq@fortunes.com.cn
* @2015年8月21日 上午1:54:09
*/
public class ConcurrentServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
// 成员变量,由于 servlet 是单例,所以每个 request 请求后的线程公用一个 servlet 实例
private String username;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 从页面获取
//String username = request.getParameter("username");
this.username = request.getParameter("username");
System.out.println("thread name = " + Thread.currentThread().getName());
System.out.println("username = " + this.username);
// 模拟业务处理
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
request.setAttribute("username", username);
System.out.println("username >>> " + this.username);
request.getRequestDispatcher("show.jsp").forward(request, response);
}
}
执行
1, 在浏览器 A(chrome)输入 :first
2,在浏览器 B(Safari)输入:second
执行的次序:先点击 chrome ,并且在10秒之内点击了 Safari ,等待执行结果。模拟并发访问。有兴趣的读者可以使用第三个浏览器访问,并且在10秒内分别提交;不出意外最后执行的结果应该是显示最后提交的那个数据。
页面执行结果:
后台打印的结果如下:
简单分析:
* 从结果可以判断出每个请求各自启动了一个线程处理请求;
* 当用户在浏览器 A(线程A) 提交 username 时候,执行到(
1 | this .username = request.getParameter( "username" ); |
),把得到的值设置到 username 上,
* 此时休眠10秒,休眠的时候浏览器 B(线程B) 也提交了,在 A请求还没执到(
request.setAttribute("username", username);
)期间 B 也执行了(
1 | request.getParameter( "username" ) |
),
* 此时 username 的值就是 B 提交的值了;
* 然后 A 休眠完成,此时 username 的值已经不是 A 请求的值了!!
*
* 解决办法:
* 不把username 定义为成员变量,而是定义为方法内部的局部变量,每个线程的都各自有自己的方法区,自然方法内的变量是不可共享了!
* 此时 username 分别处在不同的线程,互不干扰,自然也就不会相互影响了!!
这是个很简单例子,但是很能说明问题了。在以后使用 servlet 的时候就要注意了!!!
由此延伸出另一个问题:struts2的 action 会不会也有这个问题呢????
答案是不会,因为 action 类是多例的,每个 request 请求都会实例化一个 action,每个实例是在各自的线程中的,自然也就不会出现线程的安全问题了……有兴趣的你可以验证看看或者看看官方文档即可!
原文地址:http://ibeginner.sinaapp.com/index.php?m=Home&c=Index&a=detail&id=d32fe7029e8841f475b9573611230881