Servlet不是线程安全的,同样Action继承与Servlet,Action也不是线程安全的。
具体先从Servlet讲起:
JSP和Servlet默认是在JAVA多线程机制上多线程执行的,这是JSP的优势之一。也就是说,当web用户第一次访问某个Servlet时,容器根据web.xml实例化该servlet,之后的其他访问都会使用同一个servlet,这样就出现了多个用户同时访问时,可能会出现多个线程同时访问某一资源的情况,数据就可能会变得不一致。而这种线程安全问题主要由实例变量使用不当引起的。
public class ConcurrentServlet extends HttpServlet{
PrintWriter output; //实例变量
String username;
public void service(HttpServletRequest request,HttpServletResponse response) throws IOException,ServletException{
output=response.getWriter(); //潜在资源访问冲突
username=request.getParameter("username");
output.println("username:"+username);
}
}
当同时访问
http://localhost:8080/.../ConcurrentServlet?username=a
http://localhost:8080/.../ConcurrentServlet?username=b
时就产生了对实例变量output的争用,可能出现的结果:
其中一个用户得到了空白的页面,另一个用户同时得到了全部的输出,如图所示:
解决的办法:
一、设置为单线程
Servlet 实现SingleThreadModel接口,Action可以在配置文件中奖Singleton属性设置为true
Servlet:
public class ConcurrentServlet extends HttpServlet implements SingleThreadModel{...}
Action:
<bean ... singleton="true">
<property>...</property>
...
</bean>
设置为单线程,当然就不会产生多线程资源争用的问题,但是这样就使应用的性能大大降低,除非绝对必要,否则,不要这么做
二、不使用实例变量
public class ConcurrentServlet extends HttpServlet{
public void service(HttpServletRequest request,HttpServletResponse response) throws IOException,ServletException{
PrintWriter output=response.getWriter(); //将类实例变量改为类函数的局部变量,每个线程都要建立自己的变量
String username=request.getParameter("username");//参考线程的工作原理
output.println("username:"+username);
}
}
Action与Servlet遵循相同的原则
三、将不得不适用实例变量的部分,syn化
public class ConcurrentServlet extends HttpServlet{
PrintWriter output; //实例变量
String username;
public void service(HttpServletRequest request,HttpServletResponse response) throws IOException,ServletException{
Synchronized(this){
output=response.getWriter(); //潜在资源访问冲突
username=request.getParameter("username");
output.println("username:"+username);
}
}
}
Action遵循相同的原则
这样的线程安全只有在大规模并发访问的时候才可能发生,所以在开发时往往不容易发现。而问题的根源在于实例变量,所以写程序的时候要遵循以下原则:
①避免使用实例变量
②不得不使用实例变量的地方同步化
③从效率上考虑,尽量减少同步的代码量