浅析servlet线程安全性问题

前段时间去杭州面一家公司,提到了servlet线程安全性问题,由于我是应届生,经验不足,以前也没有考虑过这些东西,所以当时直接凭直觉在那么大一片空白处写上了一个连自己都发笑的synchornized,后来才发现,这个关键字也是得分点。哈哈,虽然后来谈薪水的时候被pass了, 但是我压根也没打算进去,主要是那家公司实力不强,而且办公环境超级不好。好了,说了这么的多废话,我就直接今天的笔记把。
servlet安全性问题,其实这在网上有很多资料供参考的,但是我还是要自己写一些东西,毕竟是自己在秋知道路上碰到的。
servlet默认是多线程的,它的生命周期是由Web容器负责的。当用户第一次请求某个servlet的时候,web容器先到web.xml文件中查找该servlet配置,然后找到servlet-class并实例化该servlet。而当当浏览器第二次请求该servlet的时候,web容器将不会再次实例化。即多个线程共同使用该servlet的实例。这就造成了一定的不安全因素,虽然这种不安全发生的概率不是很大,但是防范于未然是web开发中必须考虑到的。
下面为了突出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 TestServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
PrintWriter out = null;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setCharacterEncoding("GBK");
//获取参数
String param= req.getParameter("param");
//获取out实例
out = resp.getWriter();
try {
//让程序等待10秒钟后输出name
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
out.println("您传入的参数为:"+param+"
");
}
}


}


web.xml配置
	

servlet
TestServlet



servlet
/getName.do




在这个servlet,我为了能顺利暴露出servlet多线程所存在的漏洞,用了sleep(10000)。下面我通过两个不同类型的请求来说明问题:

一、不同用户请求同一servlet
·用户1请求:Http://10.81.66.173/testApp/getName.do?param=p1
---在10秒钟内,第二个用户再次请求该servlet----
·用户2请求:Http://10.81.66.173/testApp/getName.do?param=p2
---获取的结果是----
·用户1请求的响应为:
(空白)
·用户2请求的响应为:
您传入的参数为:p1
您传入的参数为:p2

二、同一用户在较小的时间间隔离多次请求统一servlet
·浏览器输入:http://10.81.66.173/testApp/getName.do?param=p
·然后拼命地按enter回车,然后等待输出结果:
响应结果为:
您传入的参数为:p
您传入的参数为:p
您传入的参数为:p
您传入的参数为:p
您传入的参数为:p
...略
可见,按了多少次,就显示多少个“您传入的参数为:p”

至于出现该问题的最终原因,网上其实也有解释,那就是java内存模型决定的。下面是我在网上截取的一段话(比我说得好,所以用别人的):
[quote]Java的内存模型JMM(Java Memory Model)JMM主要是为了规定了线程和内存之间的一些关系。根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有实例变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存由缓存和堆栈两部分组成,缓存中保存的是主存中变量的拷贝,缓存可能并不总和主存同步,也就是缓存中变量的修改可能没有立刻写到主存中;堆栈中保存的是线程的局部变量,线程之间无法相互直接访问堆栈中的变量。[/quote]

那么,既然servlet存在线程安全问题,那么如何设计安全的servlet呢?在这里普遍来就爱那个,有三种方式可以实现。但在实际使用中要酌情考虑。
·实现 SingleThreadModel 接口
public class TestServlet extends HttpServlet implements SingleThreadModel

不过考虑到系统开销问题,这种方法不赞成使用,具体原因我在这里不想多数,大家可以到网上搜一下相关资料。

·使用synchronized同步对共享数据的操作
synchronized(this){
out = resp.getWriter();
try {
//让程序等待10秒钟后输出param
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
out.println("您传入的参数为:"+param+"
");
}

这种方法可以使用,但是不建议,毕竟同步会是系统的性能、吞吐量下降很多,而且还会造成多个用户的请求发生阻塞,相比来说,下面的办法更好。
·避免使用实例变量,改为方法内的局部变量
简单的奖printwriter out = null,放在方法内定义即可。

你可能感兴趣的:(J2EE)