当前位置:编程学习 > 网站相关 >>

Servlet的线程安全

Servlet的多线程机制
 
1.  变量的线性安全:这里的变量指字段和共享数据(如表单参数值)。
将参数变量本地化。多线程并不共享局部变量,所以要尽可能地在servlet中使用局部变量。例如:String user=request.getParameter("user");
使用同步块Synchronized,防止可能异步调用的代码块,这就意味着线程需要排队处理。但要注意在使用同步块的范围要尽可能的小,不要直接在sevice方法和响应方法上使用,这样会严重影响性能。
2.  属性的线性安全:ServletContext,HttpSession,ServletRequest对象中的属性。
ServletContext(线程不安全):ServletContext可以同时进行多线程读/写属性,线程是不安全的。要对属性的读写进行同步处理或进行深度Clone()。所以在Servlet上下文中要尽量少保存会被修改(写)的数据,可以使用其他的方式在多个Servlet中共享,比如使用单例模式处理共享数据。
HttpSession(线程不安全):HttpSession在用户会话期间存在,只能在处理属于同一个Session请求的线程中被访问,因此理论上访问Session对象的属性是线程安全的。但是当用户打开同属于同一个进程的浏览窗口,对这些窗口的访问属于同一个session,会出现多次请求,需要多个工作线程来处理,可能会造成多个线程同时读写操作。这时我们就需要对属性的读写进行同步处理:使用同步块或读/写器来处理。
ServletRequest(线程安全):对于每一个请求,由一个线程来执行,都会创建一个新的ServletRequest对象,所以ServletRequest只能在一个线程中被访问。注意:ServletRequest对象在service方法的范围内是有效的,不要试图在service方法结束后仍然保存访问请求对象的引用。
3.  不要在Servlet中创建自己的线程以完成某个功能:servlet本身就是多线程,再创建线程会导致问题复杂化,会带来线程安全的问题。
 
4.  在多个Servlet中对外部对象(比如文件)修改一定要加锁,做到互斥的访问。
 
5.  javax.servlet.SingleThreadModel接口是一个标识接口,如果一个servlet实现了这个接口,那么servlet容器将保证在一个时刻仅有一个线程可以在给定的servlet实例的service方法中执行,将其他所有请求进行排队。
 
6.  服务器可以使用多个实例来处理请求,代替单个实例的请求排队带来的效益问题。服务器创建一个Servlet类的多个Servlet实例组成的实例池,对于每个请求分配Servlet实例进行响应处理,之后放回到实例池中等待下此请求。这样就造成并发访问的问题。此时,局部变量(字段)也是安全的,但对于全局变量和共享数据是不安全的,需要进行同步处理。而对于这样多实例的情况SingleThreadModel接口并不能解决并发访问问题。
 
 
  Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。
当客户端第一次请求某个Servlet时,Servlet容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。Servlet容器会自动使用线程池等技术来支持系统的运行,如图1所示。
 
 \
 
图1 Servlet线程池
 
  这样,当两个或多个线程同时访问同一个Servlet时,可能会发生多个线程同时访问同一资源的情况,数据可能会变得不一致。所以在用Servlet构建的Web应用时如果不注意线程安全的问题,会使所写的Servlet程序有难以发现的错误。
 
这样,当两个或多个线程同时访问同一个Servlet时,可能会发生多个线程同时访问同一资源的情况,数据可能会变得不一致。所以在用Servlet构建的Web应用时如果不注意线程安全的问题,会使所写的Servlet程序有难以发现的错误。
 
  Servlet的线程安全问题
 
  Servlet的线程安全问题主要是由于实例变量使用不当而引起的,这里以一个现实的例子来说明。
[java]
import javax.servlet. *; 
import javax.servlet.http. *; 
import java.io. *; 
public class Concurrent Test extends HttpServlet {PrintWriter output; 
    public void service (HttpServletRequest request, 
            HttpServletResponse response) throws ServletException, IOException { 
        String username; 
        Response.setContentType ("text/html; charset=gb2312"); 
        Username = request.getParameter ("username"); 
        PrintWriter output = response.getWriter (); 
        try{ 
            Thread. sleep (5000); //为了突出并发问题,在这设置一个延时 
        } catch (Interrupted Exception e){} 
        
        output.println("用户名:"+Username+""); 
    } 

 
 
  该Servlet中定义了一个实例变量output,在service方法将其赋值为用户的输出。当一个用户访问该Servlet时,程序会正常的运行,但当多个用户并发访问时,就可能会出现其它用户的信息显示在另外一些用户的浏览器上的问题。这是一个严重的问题。为了突出并发问题,便于测试、观察,我们在回显用户信息时执行了一个延时的操作。假设已在web.xml配置文件中注册了该Servlet,现有两个用户a和b同时访问该Servlet(可以启动两个IE浏览器,或者在两台机器上同时访问),即同时在浏览器中输入:
 
  a:http://localhost: 8080/servlet/ConcurrentTest? Username=a
 
  b:http://localhost: 8080/servlet/ConcurrentTest? Username=b
 
  如果用户b比用户a回车的时间稍慢一点,将得到如图2所示的输出:
 
 
图2 a用户和b用户的浏览器输出
 
  从图2中可以看到,Web服务器启动了两个线程分别处理来自用户a和用户b的请求,但是在用户a的浏览器上却得到一个空白的屏幕,用户a的信息显示在用户b的浏览器上。该Servlet存在线程不安全问题。下面我们就从分析该实例的内存模型入手,观察不同时刻实例变量output的值来分析使该Servlet线程不安全的原因。
 
  Java的内存模型JMM(Java Memory Model)JMM主要是为了规定了线程和内存之间的一些关系。根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有实例变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存由缓存和堆栈两部分组成,缓存中保存的是主存中变量的拷贝,缓存可能并不总和主存同步,也就是缓存中变量的修改可能没有立刻写到主存中;堆栈中保存的是线程的局部变量(save in stack, not in heap),线程之间无法相互直接访问堆栈中的变量。根据JMM,我们可以将论文中所讨论的Servlet实例的内存模型抽象为图3所示的模型。
 \
 
图3 Servlet实例的JMM模型
 
  下面根据图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线程终止。
补充:综合编程 , 安全编程 ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,