前言
当有多个客户端同时访问同一个Servlet资源时,Web服务器就会为每一个客户端创建一个线程,并且调用service(..)方法,如果同时访问了service(..)方法中的共享资源,就可能会引发线程安全问题。
代码模拟线程安全
public class Demo7 extends HttpServlet {
private static final long serialVersionUID = 1L;
//外部共享资源num
int num = 0;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
num++;
//为了模拟多线程同时访问,调用sleep()方法让当前线程休眠一会
try {
Thread.sleep(1000*3);
} catch (InterruptedException e) {
e.printStackTrace();
}
response.getOutputStream().write(("num is : "+num).getBytes());
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
打开浏览器,同时打开两个窗口,同时访问Demo7这个servlet,比如A对此资源进行了访问,A应该得到的结果是num=1;这时,B线程进来了,A线程休眠未结束,而此时的num已经被A修改为1,B继续修改,此时num=2,所以当A休眠结束,得到的结果变成了A得到的num=2;这就引发了线程安全问题;
解决方案一
加上synchronized关键字
//加上synchronized关键字,锁定当前线程
synchronized(this){
try {
num++;
Thread.sleep(1000*3);
} catch (InterruptedException e) {
e.printStackTrace();
}
response.getOutputStream().write(("num is : "+num).getBytes());
}
对线程进行锁定可以暂时解决这个问题,但是这对性能就有了影响,当A访问时,B就要在外面等着A结束才能访问;
解决方案二
public class Demo8 extends HttpServlet implements SingleThreadModel
//此接口已经被标记为过时
实现SingeleThreadModel接口,这个接口中没有定义任何方法,就如同Cloneable 、Serializable这些接口一样,都属于一种标记接口,当实现这个接口,就会每一个线程创建一个servlet对象,所以AB同时访问时,A有一个自己的servlet对象,B有一个自己的servlet对象,线程安全就解决了,但是,这也不是最好的解决方案,因为解决线程安全最好的方式是解决一个servlet对象被多个线程同时访问;就是struts2中,每一个Action都是设置为prototype类型,就是避免了线程安全问题