首先说明的是Servlet不是线程安全的。
Servlet容器在启动或第一次请求这个servlet时,Servlet容器会创建一个Servlet实例。请求完成后,servlet实例会被纳入servlet容器的线程池进行管理。所以在默认情况下,当多个请求是共享一个servlet实例的。
因此在多个线程同时访问一个servlet实例时,可能会发生多线程同时访问一个资源的情况,数据可能会变得不一致。也就存在了线程的安全问题。
package com.hg.javafan;
import javax.servlet. *;
import javax.servlet.http. *;
import java.io. *;
public class ServletTest extends HttpServlet
{
private static final long serialVersionUID = 1L;
PrintWriter output;
public void service (HttpServletRequest request,HttpServletResponse response)
throws ServletException, IOException
{
String username;
response.setContentType ("text/html; charset=GBK");
username = request.getParameter ("username");
output = response.getWriter ();
try
{
Thread.sleep (5000); //在这设置一个延时
}
catch (InterruptedException e)
{
}
output.println("用户名:"+username+"<BR>");
}
}
在web.xml中配置好servlet
同时启动两个IE窗口
a: http://localhost: 8080/servlet/test?username=a
b: http://localhost: 8080/servlet/test?username=b
输出结果:先打开的窗口没任何输出,后开打的窗口输出两行语句。
原因:Java的内存模型设计。在系统中存在一个主内存(Main Memory),java中所有的实例变量都存放在主内存中,对于所有的线程都是共享的。但是每个线程都有自己的工作内存(Working memory),工作内存由两部分组成:缓存和堆栈。
缓存中存放的是主存中变量的拷贝,缓存中的变量拷贝和主存可能存在不同步时候,就是缓存中的修改没有立即更新到主存中;
堆栈中存放的是线程局部变量。堆栈中的存放的变量,多个线程之间是不能共享的。
|
下面根据图3所示的内存模型,来分析当用户a和b的线程(简称为a线程、b线程)并发执行时,Servlet实例中所涉及变量的变化情况及线程的执行情况,如图4所示。
调度时刻 | a线程 | b线程 |
T1 | 访问Servlet页面 | |
T2 | 访问Servlet页面 | |
T3 | output=a的输出username=a休眠5000毫秒,让出CPU | |
T4 | output=b的输出(写回主存)username=b休眠5000毫秒,让出CPU | |
T5 | 在用户b的浏览器上输出a线程的username的值,a线程终止。 | |
T6 | 在用户b的浏览器上输出b线程的username的值,b线程终止。 |
从图4中可以清楚的看到,由于b线程对实例变量output的修改覆盖了a线程对实例变量output的修改,从而导致了用户a的信息显示在了用户b的浏览器上。如果在a线程执行输出语句时,b线程对output的修改还没有刷新到主存,那么将不会出以上所示的输出结果,因此这只是一种偶然现象,但这更增加了程序潜在的危险性。
由上面可以看出线程是不安全的。
解决方法
1.为每个请求创建一个servlet实例。每个实例之间不共享数据。
实现SingleThreadModel接口
public class ServletTest extends HttpServlet implements SingleThreadModel
2.读取数据的代码实现块同步,保证受保护的块一次只能一个线程访问。
synchronized (this)
{
response.setContentType ("text/html; charset=gb2312");
username = request.getParameter ("username");
output = response.getWriter ();
try
{
Thread.sleep (5000); //为了突出并发问题,在这设置一个延时
}
catch (InterruptedException e)
{
}
output.println("用户名:"+username+"<BR>");
}
3.把实例变量都定义在布局变量中(存放在堆栈上),防止多个线程之间共享变量。
public void service (HttpServletRequest request,HttpServletResponse response)
throws ServletException, IOException
{
PrintWriter output;
String username;
response.setContentType ("text/html; charset=gb2312");
username = request.getParameter ("username");
output = response.getWriter ();
try
{
Thread.sleep (5000); //为了突出并发问题,在这设置一个延时
}
catch (InterruptedException e)
{
}
output.println("用户名:"+username+"<BR>");
}