Servlet、Struts1 和Struts2 线程安全问题总结

来源:互联网 发布:丝丝缠绵 白白知 小说 编辑:程序博客网 时间:2024/06/01 23:20

面试的时候遇到这个问题,现在分享下。

Struts1:

        当第一次*.do请求过来时,在内存中的actionmapping中找到相对应的action,然后new出这个action放在缓存中,当第二次一样的请求过来时,还是找这个action,所以对于struts1来说,action是单实例的 ,只有一个,如果在action中定义变量,就要非常小心了,因为并发问题,可能带来灾难性的后果,也不是不可以,我们可以加锁达到同步,只是在性能上就要折衷了。

 

Struts2:

        每次请求过来都会new一个新的action , 所以说struts2的action是线程安全的 , 但同时也带来一个问题,每次都new一个action ,这样action的实例太多 , 在性能方面还是存在一定的缺陷的。


首先我们必须要先了解servlet的生命周期:

服务器只创建每个servlet的单一实例,首次创建servlet时,它的init方法会被调用,因此,init是放置一次性设置代码的地方,之后,针对每个用户的请求都会创建一个线程,该线程调用前面创建的实例方法。多个并发请求一般会导致多个线程同时调用service(线程安全),service方法会依据接受到HTTP请求的类型,调用doXXX方法。最后如果服务器卸载某个servlet就会调用servlet的destroy方法。

 

servlet是否线程安全?

通常情况下,系统只生成servlet的单一实例之后,为每个用户请求建立新的线程。如果很多请求同时到来,那么多个线程可能会并发的访问同一个servlet对象。因此我们必须小心地同步对字段以及全局变量和其它共享数据的访问,因为多个线程可能同时对同一数据进行访问。(多个线程并不共享局部变量)。


解决方法:

原则上,可以让servlet实现singleThreadModel接口,阻止多线程访问

Public class Servlet1 extends HttpServlet implements SingleThreadModel{

……..

}

实现此接口。大多数情况下,系统将所有的请求排队,一次只将一个请求发送给单个servlet实例。但是,服务器也可以创建由多个实例组成的池,同一时间每个实例都能够请求处理,此时我们不需要担心队servlet常规字段(实例变量)的同时访问,但是必须同步对类变量或存储在servlet之外的共享数据的访问,

大部分情况下,SingleThreadModel并不是一个很好的选项。如果servlet被频繁访问,那么同步对servlet的访问对性能造成极大的损害(等待时间)。在servlet等待I/O,数据库访问等其它操作时,服务器不能处理同一servlet的挂起请求。

singleThreadModel的第二个问题来源于,Servlet规范允许服务器使用多个实例来处理请求来替代对单个实例的请求进行排队的方案。只要每个实例同一时间处理一个请求,实例共享的方式就满足规范的要求,但这不是一个好的方案:

当使用非静态实例变量存储共享数据,singleThreadModel会阻止并发的访问,但是每个servlet实例都拥有实例变量的单独副本,数据就不能共享,

使用静态实例变量存储共享数据:singleThreadModel的共享池也就没有任何优点,多个请求依旧会并发的访问静态数据。

对于高流量的servlet,使用明确的代码同步更好一些。

 

以一个简单的例子说明,给出3种方案

String id = “user id =” + userId;

Do something….

userId = userId + 1;

1:减少竞争

Sting id = “user id =” + userId++;这种方案减低了产生不正确答案的可能性,但没有完全消除,在很多情况下,减低错误出现的可能性是一场灾难,问题在测试中更难以发现

2:SingleThreadModel

将servlet做如下修改

public class UserId extends HttpServlet implements SingleThreadModel{

如果服务器是通过排队所有的请求来实现SingleThreadModel,那它能工作,但如果村子大量的并发访问,这种方式会导致性能上的巨大损失。

如果服务器通过产生servlet的多个实例来实现SingleThreadModel。这种方式会完全失败,因为每个实例都拥有自己的userId字段。

3:使用同步

Synchronized(this){

       String id = “user id =” + userId;

Do something….

userId = userId + 1;

 

}

  

Struts 1 和 Struts 2 的线程安全

在最近使用struts2的时候,都会看到别人写的action中会加上一个scope("prototype") 
这很是奇怪。为什么要加这个。。 
在struts1中并不会加这个。。而且对于struts1中的action都是单例的。虽然听说Singleton是个反模式,听说有性能问题。但在使用struts1的时候没有去深究,因为大家都这么用,用得很自然。 

在查询资料的时候又看到了很多没有加上scope的问题 
http://blog.csdn.net/foamflower/archive/2009/07/08/4329989.aspx 
http://elf8848.javaeye.com/blog/356746 
这些都是因为没有加上scope,导致上一次的变量值影响了下一次的请求。 
还有如下问题:也让我想不通。 

引用
《strut2权威指南》中有这么一段: 
线程模式方面的对比:Struts1 Action是单例模式并且必须是线程安全的,因为仅有Action的一个实例来处理所有的请求。单例策略限制了Struts1 Action能做的事,并且要在开发时特别小心。Action资源必须是线程安全的或同步的;Struts 2 Action对象为每一个请求产生一个实例,因此没有线程安全问题 。 

由于对设计模式不太了解,做了这么之年Struts,一直不知道struts1是单例模式,不知这一点是从哪体现出来的? 
而在开发过程中要怎样注意“线程安全或同步”?struts2从哪体现的“没有线程安全问题”?

那么线程的问题到底体现在哪儿呢? 
在查了些资料后。。明白了些! 
http://hi.baidu.com/platon/blog/item/64a20ff3f96e7fce0b46e031.html 
这文章中讲了最基本的servlet的多线程问题。 
其实servlet本身就是个多线程环境下的单例类。web容器就是这样来处理servlet的。 

通过这文章知道,为什么struts1中并没有考虑到线程问题,因为所有的代码都是写在execute的方法中,所有变量都是定义在里面,所以没有线程安全问题。 

而现在的struts2就不一样了。struts2的action中就像一个POJO一样,定义了很多的类变量。这就有线程安全问题了。。此时,就使用scope=prototype来指定是个原型模式,而不是单例,这样就解决了线程安全问题。每个线程都是一个新的实例。。 

http://hi.baidu.com/niujunkai/blog/item/021964adc130660a4a36d6ab.html 
这文章也说明了问题的关键之处。 
引用
但是,线程同步是不得以的方法,是比较复杂的,而且会带来性能的损失。等效的代码中,不需要同步在编写容易度和性能上会更好些。 
我这里强调的是什么代码是始终为线程安全的、是不需要同步的。如下: 
1)常量始终是线程安全的,因为只存在读操作。 
2)对构造器的访问(new 操作)是线程安全的,因为每次都新建一个实例,不会访问共享的资源。 
3)最重要的是:局部变量是线程安全的。因为每执行一个方法,都会在独立的空间创建局部变量,它不是共享的资源。局部变量包括方法的参数变量。 
struts user guide里有: 
Only Use Local Variables - The most important principle that aids in thread-safe coding is to use only local variables, not instance variables , in your Action class. 
译:只使用用局部变量。--编写线程安全的代码最重要的原则就是,在Action类中只使用局部变量,不使用实例变量。

struts2本身就是多实例单线程的,所以可以说 本身就是线程安全的。
struts1则是单实例多线程的,所以必须要注意线程的安全性。
 所以相对来说,struts1 要多使用局部变量,而struts2使用 实例变量则不会产生不安全的结果

总的来说,Struts1 的 Action 是单例模式,因此开发者必须保证它是线程安全的或是同步的,因为Struts 1中每个Action仅有一个实例来处理所有的请求。

    但是在用Struts 1开发时并没有考虑到线程安全问题,这是因为我们在Action中使用的基本都是局部变量,而“局部变量是线程安全的。因为每执行一个方法,都会在独立的空间创建局部变量,它不是共享的资源。局部变量包括方法的参数变量”(忘了引自哪了)。在Struts 1中,所有的变量都是定义在Action中我们要执行的方法里的(Action中的execute方法或DispatchAction中指定要执行的方法),我们用于封装客户端请求参数的ActionForm,也是作为一个参数传入,也属于局部变量,因此,不存在线程安全问题。

 

    Struts 2 的 Action 对象为每一个请求产生一个实例,因此,虽然在Action中定义了很多全局变量,也不存在线程安全问题。


    Struts 2框架在处理每一个用户请求的时候,都建立一个单独的线程进行处理,值栈ValueStack也是伴随着局部线程而存在的。在该线程存在过程中,可以随意访问值栈,这就保证了值栈的安全性。

 

    在Struts 2中,ActionContext是一个局部线程,这就意味着每个线程中的ActionContext内容都是唯一的。所以开发者不用担心Action的线程安全。

*****************************************************************************************************************************************************************
Struts 1.x在第一次请求某个Action时,会创建这个Action实例。但之后再请求该Action实例时,就用之前创建好的这个Action处理,即它是单例模式。所以Struts 1.x的Action不是线程安全的。 

但是为什么我们在使用Struts 1.x开发的时候一般不用考虑线程安全问题呢? 
那是因为Struts 1.x的方法调用模式用到的参数一般都是局部变量(包括request, response等,如下面的方法定义),局部变量是线程安全的,因此不存在线程安全问题。 
但是要是在execute中使用了实例变量,就会存在线程安全问题。所以我们用Struts 1.x开发时尽量不要使用实例变量, 

如果一定要用或者有这种需要,那么我们一定要意识到使用实例变量是存在线程安全的,可以使用同步机制去处理线程安全问题。 
public ActionForward execute (ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws ActionException; 

Struts 2 Action对象为每一个请求产生一个实例,因此没有线程安全问题 。 
在Sping + Struts 2的应用中,由Spring来管理Struts 2的Action,而IoC容器管理的bean默认是单实例的 

(scope="singleton"),加上Struts 2的Action就像一个POJO一样,定义了很多的类变量,这就有线程安全问题了。解决些问题最简单的办法就是不使用单例模式(设置scope="prototype")。 
<bean id="testAction" class="com.webapp.action.TestAction" scope="prototype"> 

引言: 
但是,线程同步是不得以的方法,是比较复杂的,而且会带来性能的损失。等效的代码中,不需要同步在编写容易度和性能上会更好些。 
我这里强调的是什么代码是始终为线程安全的、是不需要同步的。如下: 
1)常量始终是线程安全的,因为只存在读操作。 
2)对构造器的访问(new 操作)是线程安全的,因为每次都新建一个实例,不会访问共享的资源。 
3)最重要的是:局部变量是线程安全的。因为每执行一个方法,都会在独立的空间创建局部变量,它不是共享的资源。局部变量包括方法的参数变量。



原创粉丝点击