logo资料库

深入研究Servlet线程安全性问题.doc

第1页 / 共5页
第2页 / 共5页
第3页 / 共5页
第4页 / 共5页
第5页 / 共5页
资料共5页,全文预览结束
深入研究 Servlet 线程安全性问题 2005-05-23 09:18作者:雷军环出处:计算机与信息技术责任编辑:方舟 摘 要:介绍了 Servlet 多线程机制,通过一个实例并结合 Java 的内存模型说明引起 Servlet 线程不安全的原因,给出了保证 Servlet 线程安全的三种解决方案,并说明三种方案 在实际开发中的取舍。 关键字:Servlet 线程安全 同步 Java 内存模型 实例变量 Servlet/JSP 技术和 ASP、PHP 等相比,由于其多线程运行而具有很高的执行效率。由 于 Servlet/JSP 默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线 程的安全性问题。然而,很多人编写 Servlet/JSP 程序时并没有注意到多线程安全性的问题, 这往往造成编写的程序在少量用户访问时没有任何问题,而在并发用户上升到一定值时,就 会经常出现一些莫明其妙的问题。 Servlet 的多线程机制 Servlet 体系结构是建立在 Java 多线程机制之上的,它的生命周期是由 Web 容器负责 的。当客户端第一次请求某个 Servlet 时,Servlet 容器将会根据 web.xml 配置文件实例化 这个 Servlet 类。当有新的客户端请求该 Servlet 时,一般不会再实例化该 Servlet 类,也就 是有多个线程在使用这个实例。Servlet 容器会自动使用线程池等技术来支持系统的运行, 如图1所示。 图1 Servlet 线程池 这样,当两个或多个线程同时访问同一个 Servlet 时,可能会发生多个线程同时访问同 一资源的情况,数据可能会变得不一致。所以在用 Servlet 构建的 Web 应用时如果不注意 线程安全的问题,会使所写的 Servlet 程序有难以发现的错误。 Servlet 的线程安全问题 Servlet 的线程安全问题主要是由于实例变量使用不当而引起的,这里以一个现实的例 子来说明。 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"); 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),工作内存由缓存和堆栈两部分组成,缓存中保存的是主存中变量的拷贝,缓存可 能并不总和主存同步,也就是缓存中变量的修改可能没有立刻写到主存中;堆栈中保存的是 线程的局部变量,线程之间无法相互直接访问堆栈中的变量。根据 JMM,我们可以将论文 中所讨论的 Servlet 实例的内存模型抽象为图3所示的模型。 图3 Servlet 实例的 JMM 模型 下面根据图3所示的内存模型,来分析当用户 a 和 b 的线程(简称为 a 线程、b 线程) 并发执行时,Servlet 实例中所涉及变量的变化情况及线程的执行情况,如图4所示。 a 线程 调 度 时 刻 T1 T2 访问 Servlet 页面 output=a 的 输 出 username=a 休 眠 5000毫秒,让出 CPU 在用户 b 的浏览器上 输 出 a 线 程 的 username 的 值 ,a 线 程终止。 T3 T4 T5 T6 b 线程 访问 Servlet 页面 output=b 的 输 出 ( 写 回 主 存)username=b 休眠5000 毫秒,让出 CPU 在用户 b 的浏览器上输出 b 线程的 username 的值,b 线 程终止。 图4 Servlet 实例的线程调度情况 从图4中可以清楚的看到,由于 b 线程对实例变量 output 的修改覆盖了 a 线程对实例变 量 output 的修改,从而导致了用户 a 的信息显示在了用户 b 的浏览器上。如果在 a 线程执 行输出语句时,b 线程对 output 的修改还没有刷新到主存,那么将不会出现图2所示的输出 结果,因此这只是一种偶然现象,但这更增加了程序潜在的危险性。
设计线程安全的 Servlet 通过上面的分析,我们知道了实例变量不正确的使用是造成 Servlet 线程不安全的主要 原因。下面针对该问题给出了三种解决方案并对方案的选取给出了一些参考性的建议。 1、实现 SingleThreadModel 接口 该接口指定了系统如何处理对同一个 Servlet 的调用。如果一个 Servlet 被这个接口指 定,那么在这个 Servlet 中的 service 方法将不会有两个线程被同时执行,当然也就不存在线 程安全的问题。这种方法只要将前面的 Concurrent Test 类的类头定义更改为: Public class Concurrent Test extends HttpServlet SingleThreadModel { ………… } implements 2、同步对共享数据的操作 使用 synchronized 关键字能保证一次只有一个线程可以访问被保护的区段,在本论文 中的 Servlet 可以通过同步块操作来保证线程的安全。同步后的代码如下: ………… Public class Concurrent Test extends HttpServlet { ………… Username = request.getParameter ("username"); Synchronized (this){ Output = response.getWriter (); Try { Thread. Sleep (5000); } Catch (Interrupted Exception e){} output.println("用户名:"+Username+"
"); } } } 3、避免使用实例变量 本实例中的线程安全问题是由实例变量造成的,只要在 Servlet 里面的任何方法里面都 不使用实例变量,那么该 Servlet 就是线程安全的。 修正上面的 Servlet 代码,将实例变量改为局部变量实现同样的功能,代码如下: …… Public class Concurrent Test extends HttpServlet {public void service (HttpServletRequest request, HttpServletResponse
Response) throws ServletException, IOException { Print Writer output; String username; Response.setContentType ("text/html; charset=gb2312"); …… } } 对上面的三种方法进行测试,可以表明用它们都能设计出线程安全的 Servlet 程序。但 是,如果一个 Servlet 实现了 SingleThreadModel 接口,Servlet 引擎将为每个新的请求创 建一个单独的 Servlet 实例,这将引起大量的系统开销。SingleThreadModel 在 Servlet2.4 中已不再提倡使用;同样如果在程序中使用同步来保护要使用的共享的数据,也会使系统的 性能大大下降。这是因为被同步的代码块在同一时刻只能有一个线程执行它,使得其同时处 理客户请求的吞吐量降低,而且很多客户处于阻塞状态。另外为保证主存内容和线程的工作 内存中的数据的一致性,要频繁地刷新缓存,这也会大大地影响系统的性能。所以在实际的 开发中也应避免或最小化 Servlet 中的同步代码;在 Serlet 中避免使用实例变量是保证 Servlet 线程安全的最佳选择。从 Java 内存模型也可以知道,方法中的临时变量是在栈上 分配空间,而且每个线程都有自己私有的栈空间,所以它们不会影响线程的安全。 小结 Servlet 的线程安全问题只有在大量的并发访问时才会显现出来,并且很难发现,因此 在编写 Servlet 程序时要特别注意。线程安全问题主要是由实例变量造成的,因此在 Servlet 中应避免使用实例变量。如果应用程序设计无法避免使用实例变量,那么使用同步来保护要 使用的实例变量,但为保证系统的最佳性能,应该同步可用性最小的代码路径。
分享到:
收藏