我们在开发JAVA WEB应用程序,大多都会考虑用MVC模式的框架来部署(相信没有程序员再考虑前两者简单的模式了吧。),这也是基于下面的原因: 一、MVC由于分层清晰,容易看到整个系统流程的架构,对于越来越复杂的系统是有相当大的帮助。 二、 扩展性,耦合性低。各层影响相当少,如JSP页面只负责数据显示,M负责业务逻辑处理,C相当于Servlet来控制流程的转向。等等这一系列的好处。 用MVC的模式,其本质就是用Servlet的应用技术。Servlet/jsp和其他如ASP\PHP语言相比,由于使用了多线程运行技术与具有很高的执行效率。但是也就是Servlet由于默认多线程模式执行,依我们所了解线程安全性问题,也就不得不要考虑在Servlet中也存在这样的问题,然而,很多程序员只专注于业务逻辑的处理,并没有注意到多线程的安全性的问题(在此编写之前,我也存在这样的经历,不过还好。。。。),这往往造成编写的程序在用户量少的时候没出什么问题,而一旦发现大量的并发用户时,而且这数量达到一定的数量时,就会出现一系列莫名的问题,这问题在下面我们可以看的到。 Servlet的多线程机制是怎么样的呢: Servlet体系结构是建立在JAVA的多线程机制上的,但它的生命周期是由WEB容器来管理的,当客户端第一次请求某个Servlet时,Servlet容器会根据web.xml的配置实例化相应的Servlet类,当有新的客户端来请求这个Servlet时,容器一般不会再实例化这个Servlet类,而是以线程方式去调用这个实例的方法,然后再有更多的客户端来请求时,就存在了多个线程在使用这个实例。并且Servlet容器会自动使用线程池技术来支持系统的运行。 在这样的情况,当两个或者多个客户端同时请求同一Serlvet时,就会存在多个线程同时访问同一资源的情况,数据就可能变的不一致,所以在用Servlet搭建WEB应用程序时如果不考虑线程的问题,就会出现难以发现的问题。 Servlet线程安全问题的例子: Servlet线程是由于使用实例变量不当而导致的,这里有如下的例子: 代码程序如下: public class SecurityTest extends HttpServlet { PrintWriter output;//成员变量
@Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=gb2312"); String name = request.getParameter("name"); output=response.getWriter(); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } output.write(name); }
} 该实例中定义了一个实例变量output,在service方法中负责输出用户名,当一个用户访问该Servlet时,程序会正常的运行,但当多个用户并发的访问时,就可能会出现其他的用户信息显示在另一个用户的浏览器上的问题,为了看到实际的效果,在这个程序中,我特做如下的处理:就是延时5000毫秒,让第一个用户暂停在输出数据前。然后我们马上发起另一个请求,这种情况下就会出现如下的页面 第一次请求:http://127.0.0.1:8080/Test2/securityTest?name=a大家看到这页面什么数据也没有,就是说那姓名没有打印出来,那跑到哪里去了呢?看第二个用户请求的情况。 第二个用户请求地址:http://127.0.0.1:8080/Test2/securityTest?name=b
可以看到,原来a值已经打印到第二个用户的浏览器了。 可以想像在暂停5000时间里,第二个用户请求这个servlet,已经把output的引用变成了第二个用户请求的output值了,这样就解释了为什么会输出到第二个客户端的浏览的原因。
从内存模型来看Servlet的线程安全问题//不是很理解这一段 JAVA的内存模型JMM(JAVA Memory Model)主要是为了规定线程与内存的一些关系,既然Servlet也是用线程技术,那么我们也从这方面寻找根本原因,根据JMM,系统存在有主内存,JAVA的实例变量(就是类变量吧)都是存在于主内存供其他内存使用,也就是对所有的线程都是共享的,而根据线程的特点:它是有自己的工作内存的,工作内存包括缓存和堆栈两部分,堆栈是专门用来存在方法中的局部变量的,而缓存则是主内存中变量的拷贝,缓存与主内存并不总是同步的,也就是缓存中的变量的修改可能没有立刻写到主存中,如下图:
根据内存模型,我们可以得到如下的线程调度表: 调度时刻a线程b线程T1访问Servlet页面T2 访问Servlet页面T3 output=a的输出username=a休眠5000毫秒,让出CPU T4 output=b的输出(写回主存)username=b休眠5000毫秒,让出CPUT5 在用户b的浏览器上输出a线程的username的值,a线程终止。T6在用户b的浏览器上输出b线程的username的值,b线程终止。可以看出,由于b线程对实例变量output的修改覆盖了a线程对实例变量output的值,直接导致了用户a的信息显示到b的浏览器上。根据内存模型,我们也可以解释到,正是因为b开始时修改了缓存中的output值,然后刷新到主内存中,而又有足够的时间刷新到a缓存中,这时a的Output值就直接导致了指向b浏览器。 解决方法: 从上面的分析中,我们知道导致线程不安全的主要原因在于实例变量的使用不当,下面就提出如下三种解决方法 第一,让Servlet类实现SingleThreadModel接口,该接口指定系统如何处理对同一个Servlet的调用,如果一个Servlet被指定实现这个接口,那么,在这个Servlet中的service方法将不会在两个线程中同时执行,也就是说执行完一个后再执行下一个请求的service,当然也就不存在线程不安全的问题了。 代码如下: public class SecurityTest extends HttpServlet implements SingleThreadModel{} 其实这方法也就相当于是同步方法的效果吧。
第二.同步对共享数据的操作。我们所熟悉的就是用synchronized关键字,这样能保证一次只有一个线程来操作被保护的区段。在本例子也可以用synchronized来保证线程的安全,代码如下: public class SecurityTest extends HttpServlet { PrintWriter output;
@Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=gb2312"); String name = request.getParameter("name"); Synchronized(this){ output=response.getWriter(); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } output.write(name); } }} 第三:避免使用实例变量,而使用局部变量。因为Servlet线程不安全的原因是由实例变量引起,所以我们可以避免使用实例变量,而使用局部变量,线程之间很难直接访问局部变量 ,这样就从根本上解决了这一问题。 在本例子中,就是将output放在service方法中当局部变量 。 public class SecurityTest extends HttpServlet {
@Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter output; response.setContentType("text/html;charset=gb2312"); String name = request.getParameter("name"); output=response.getWriter(); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } output.write(name); }} 这三种方法都对解决servlet线程安全起到很好的作用,但我们如果对他们进行比较一下,看哪一种更适合呢: 第一个方案中:实现SingleThreadModel接口,Servlet引擎将为每个客户请求都生成一个Servlet实例,这将引起大量的系统开销,在新版本的Servlet2.4中也不提倡使用了。 第二个方案中:在程序中使用同步来保护要使用的共享数据,也使系统的性能大大的下降,这是因为被同步的代码在同一时刻只能由一个线程来执行,使得同时处理其他客户请求的吞吐量大大降低,大量客户处于阻塞状态,这对于并发用户请求来说并非是一件很好的事情。另外为了保持主内存与工作内存数据的一致性要频繁地刷新缓存,这也大大降低了系统性能,所以这种方案不大可取。 第三个方案则应该是最优方案:从JAVA内存模型来看,方法中的临时变量都是在栈中分配空间,而每个线程都有自己的私有栈空间,互不干扰,不会影响性能,也不会产生线程安全的问题。
|
相关推荐
标题中的“论坛转帖工具.rar”表明这是一个用于在论坛之间转移帖子的软件工具,通常用于帮助用户方便地将一个论坛的帖子内容复制到另一个论坛,可能是为了分享信息、讨论或保存重要的帖子。这类工具可能包括自动抓取...
使用此类工具时,用户需要确保其安全性,因为任何第三方工具都可能存在潜在风险,如账号安全问题、软件携带病毒等。同时,遵循贴吧社区的规定,合理、合法使用转帖工具,避免滥用导致账号被封禁。 此外,工具的使用...
UBB论坛转帖圣手.exeUBB论坛转帖圣手.exe
4. **日志记录**:为了便于管理和跟踪,插件可能还会记录转帖操作,生成日志供管理员查看,以便了解转帖历史和排查问题。 5. **兼容性**:作为正式版插件,它应该经过了严格的测试,与 PHPwind 7.5 版本保持良好的...
在IT行业中,编辑人员在处理图像或视频时经常会遇到水印问题。水印可能是他人版权的标识,也可能是不希望展示的信息,去除水印成为了一项必要的技能。本篇文章将详细探讨“编辑人员转帖去水印工具”,并介绍如何使用...
X2转帖工具、采集工具”是针对这个平台设计的辅助软件,主要用于帮助论坛管理员或用户批量发布帖子和采集内容,提高论坛内容更新的效率。 一、批量发帖功能 1. 自动化发布:此工具可以自动化地创建和发布帖子,...
编程大赛通常由诸如ACM(美国计算机协会)、Google Code Jam、Facebook Hacker Cup等国际知名组织举办,这些比赛不仅考验参赛者的编程速度和准确性,更注重算法的优化、问题解决的效率以及代码的优雅性。例如,ACM-...
《一键转帖功能插件 for 帝国CMS 6.0 GBK utf8 V1.0》 本文将深入探讨“一键转帖功能插件”在帝国CMS 6.0系统中的应用与实现,该插件适用于GBK及UTF-8编码环境,旨在提升网站内容的分享与传播效率。我们将从安装...
【标题】:“用C# Generator解决Hanoi塔问题”揭示了如何使用C#编程语言来构建一个自动化生成器,以高效地处理经典的汉诺塔问题。汉诺塔问题是一个著名的递归问题,它涉及到将一组盘子从一根柱子移动到另一根柱子,...
- 注意版权问题,未经允许不要随意转帖他人的内容。 - 若遇到问题,可参考Readme文档或联系开发者寻求帮助。 总之,Html2UBBMaxcj_Softii论坛专用转帖工具是一个实用的工具,解决了HTML内容在特定论坛环境下无法...
1.修改自Convert X转帖工具 2.新增批量替换关键词(原来是单个词语替换,可以利用这个功能删除一些网站的防转帖代码) 3.批量随机新增文字(新增内容可自定义,从而实现伪原创) 4.cookie记录替换和新增关键词(避免每次...
转帖图片提取工具可以对论坛图片附件信息进行清除,只保留图片代码,操作很简单,推荐有需要转帖图片工具的朋友下载 转帖图片提取工具使用方法: 将IP138上处理过的东西复制到上方的编辑框内,点击只要图片,下面...
"一键转帖功能插件 for 帝国CMS v1.0.rar" 是一个专为帝国CMS设计的扩展工具,其主要目标是简化用户在网站上分享内容的过程,提高用户体验。这个插件允许用户轻松地将网站上的文章或信息复制并转发到其他平台,如...
看到论坛里帖子由精美的图片想转过来,或者批量提取地址时很好用
总的来说,理解和熟悉GL8雨刮系统的结构和工作原理,能够帮助车主及时识别并解决可能出现的问题,避免不必要的更换部件,提高车辆的使用体验和安全性。在进行任何维修操作之前,确保断开电源,以防电击,并参照车辆...
在实际应用中,性能测试可以帮助企业解决一些常见的问题,如电信计费软件在高峰期能否承受大量的并发用户同时访问,或者某些应用系统能否承受大量用户的访问等问题。通过科学的软件测试手段和先进的测试工具,可以...
【网页复制限制】 网页复制限制是许多网站为了保护其内容不被随意复制而采取的一种措施...不过,需要注意的是,这些方法可能涉及版权和隐私问题,因此在实际操作时应尊重网站的版权政策,并确保合法合规使用网络资源。