Servlet线程安全问题学习总结

来源:互联网 发布:阮铭 两少一宽 知乎 编辑:程序博客网 时间:2024/06/07 11:32

首先说明的是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>"); 
    }

原创粉丝点击