servlet是单例的 所以需要线程安全 以及如何实现线程安全

Servlet/JSP技术和ASP、PHP等相比,由于其多线程运行而具有很高的执行效率。由于Servlet/JSP默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的安全性问题。然而,很多人编写Servlet/JSP程序时并没有注意到多线程安全性的问题,这往往造成编写的程序在少量用户访问时没有任何问题,而在并发用户上升到一定值时,就会经常出现一些莫明其妙的问题。

  Servlet的多线程机制

 

  Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。当客户端第一次请求某个Servlet时,Servlet容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。Servlet容器会自动使用线程池等技术来支持系统的运行,如图1所示。

servlet是单例的 所以需要线程安全 以及如何实现线程安全

图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, www.2cto.com

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+"<BR>");

}

}

  该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所示的输出:

servlet是单例的 所以需要线程安全 以及如何实现线程安全

图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所示的模型。

servlet是单例的 所以需要线程安全 以及如何实现线程安全

图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线程终止。

T6 在用户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 implements SingleThreadModel {

…………

}

  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+"<BR>");

}

}

}

  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/JSP技术和ASP、PHP等相比,由于其多线程运行而具有很高的执行效率。由于Servlet/JSP默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的安全性问题。然而,很多人编写Servlet/JSP程序时并没有注意到多线程安全性的问题,这往往造成编写的程序在少量用户访问时没有任何问题,而在并发用户上升到一定值时,就会经常出现一些莫明其妙的问题。

  Servlet的多线程机制

 

  Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。当客户端第一次请求某个Servlet时,Servlet容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。Servlet容器会自动使用线程池等技术来支持系统的运行,如图1所示。

servlet是单例的 所以需要线程安全 以及如何实现线程安全

图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+"<BR>");

}

}

  该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所示的输出:

servlet是单例的 所以需要线程安全 以及如何实现线程安全

图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所示的模型。

servlet是单例的 所以需要线程安全 以及如何实现线程安全

图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线程终止。

T6 在用户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 implements SingleThreadModel {

…………

}

  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+"<BR>");

}

}

}

  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 内存模型也可以知道,方法中的临时变量是在栈上分配空间,而且每个线程都有自己私有的栈空间,所以它们不会影响线程的安全。

对于存在线程不安全的类,如何避免出现线程安全问题:

  1、采用synchronized同步。缺点就是存在堵塞问题,可以使用一下读锁和写锁。

  2、使用ThreadLocal(实际上就是一个HashMap),这样不同的线程维护自己的对象,线程之间相互不干扰。

分类:默认分类 时间:2012-01-05 人气:2
本文关键词:
分享到:

相关文章

  • JavaWeb编程从零开始 Servlet的基本配置 2012-02-04

    学习JavaWeb的人没有不知道Servlet的吧,而要用Servlet就需要在web.xml中进行配置。相信有很多初学者跟我当初一样,对于一些配置参数不是很理解,今天就说说Servlet最基本的配置信息。 下面是一个最基本的Servlet配置: <servlet> <servlet-name>MyServlet</servlet-name> <servlet-class>com.Servlet.MyServlet</servlet-clas

  • Servlet生成验证码图片 2012-03-24

    1.验证码 -- 平台项目帐号登录时常用到的,有一定安全防范君子的报障,可防范没有什么技术小人呵呵,开玩笑不要认真,可能对初学者有帮助,有需要拿去吧 整理不易,转载请注明出处:Servlet生成验证码图片 2.配置,主要是web.xml的配置,如下: RandomCode com.zuidaima.servlet.RandomCode RandomCode /http://www.2cto.com/uploadfile/Collfiles/20140329/20140329105901325.

  • Servlet及JSP中的多线程同步问题 2012-04-06

      Servlet/jsp(SUN企业级应用的首选)技术和ASP、PHP等相比,由于其多线程运行而具有很高的执行效率。由于Servlet/jsp(SUN企业级应用的首选)默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的同步问题。然而,很多人编写Servlet/jsp(SUN企业级应用的首选)程序时并没有注意到多线程同步的问题,这往往造成编写的程序在少量用户访问时没有任何问题,而在并发用户上升到一定值时,就会经常出现一些莫明其妙的问题,对于这类随机性的问题调试难度也很大。  

  • jQuery调用Servlet方法及注意事项 2012-04-19

    虽然现在很多网站用框架了,但Servlet还是JAVA初学者必学的知识点,对于普通的小网站用Servlet也能满足需要,以下是如何用MyClipse创建Servlet的步骤及注意事项: 第一步:创建Servlet,可用File->New->other->输入servlet,然后用向导来做,若无特别需要,只要生成get和post方法即可, 注意:Servlet-name就是servlet的class名称,Servlet-Class就是包名.类名,Servlet Maapings中

  • Struts2框架与servlet共存问题 2012-05-03

    今天在做一个项目的时候遇到了一个很奇怪的问题,这个项目的一部分是之前用servlet写的,但是后边打算用struts写,再加入struts框架配置后发现原先的那部分不能正常访问了,之前没这么做过所以很纳闷,struts2配置自认为写的很熟了,应该没什么问题啊,struts.xml文件改了又改还是找不到错误,后来在看到web.xml时突然眼前一亮。。 原来是所有的请求都被struts2拦截了,struts2把servlet当成action了,因为servlet和action都是没有后缀的。 很是悲

  • servlet学习(1) 2012-05-24

    1.Servlet是sun公司提供的一门用于开发动态web资源的技术。 2.Servlet在web应用的位置: 3.创建Servlet的三种方式: (1)实现servlet的接口 (2)继承GenericServlet,覆写service方法 (3)继承HttpServle,覆写doGet()或者doPost()等方法 注意: 1>GenericServlet实现了 Servlet 和ServletConfig接口. GenericServlet可以直接被一个servlet扩充,尽管它更为

  • Oracle知识-HTTP Servlet中持久化状态 2012-06-14

      HTTP协议的无状态   1. 在HTTP协议中无状态的优缺点   a) HTTP交互是无状态的   无状态是指,当浏览器发送请求给服务器的时候,服务器响应,但是同一个浏览器再发送请求给服务器的时候,他会响应,但是他不知道你就是刚才那个浏览器。   b) 优点   i. 客户浏览器不会注意到服务器出现故障并重启.   ii. 在服务器不需要先前信息时它的应答就较快   c) 缺点   i. 对于事务处理没有记忆能力,可能导致每次连接传送的数据量增大   ii. 很难产生收集信息去产生良好用户

  • Android网络(3):HttpClient作客户端,Tomcat Servlet作服务器的交互示例 2012-09-22

    前面相继介绍了Android网络编程里的Socket传输图片、HttpURLConnection,今天看HttpClient. 第一部分:JavaEE版的Eclipse配置Tomcat 【备注:开发后台服务器用Eclipse的JavaEE版最好的,但单就Tomcat来说(不写jsp之类的),本文下面的服务器方面操作在普通版的Eclipse也是可以的。我这里为了和ADT-bundle分开,特意重新安个JavaEE版的Eclipse。】 1、下载Eclipse的Tomcat插件:http://www

  • Servlets和JSP Pages最佳实践 2012-11-22

    Java Servlet技术与jsp(SUN企业级应用的首选)技术使Java服务器端技术,目前他们控制了整个服务器端Java技术市场,并且逐渐成为构建商业Web应用的标准。Java开发者喜欢这些技术是由于很多的原因,包括:这些技术很容易学习,一次编写,处处运行(Write Once, Run Anywhere)。更重要的是,如果更高效地采用了下面的实践,Servlet与jsp(SUN企业级应用的首选)能够帮助分开Web的表示与内容。“最佳实践”是被证明为开发高质量、可重用与易维护的基于Servl

Copyright (C) quwantang.com, All Rights Reserved.

趣玩堂 版权所有 京ICP备15002868号

processed in 0.063 (s). 10 q(s)