`

servlet多线程与struts多线程

 
阅读更多
Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。当客户端第一次请求某个Servlet时,Servlet容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。 这样,当两个或多个线程同时访问同一个Servlet时,可能会发生多个线程同时访问同一资源的情况,数据可能会变得不一致。所以在用Servlet构建的Web应用时如果不注意线程安全的问题,会使所写的Servlet程序有难以发现的错误。

struts下的action也类似,同样在多线程环境下。为多线程环境编写代码。我们的controller servlet指挥创建你的Action 类的一个实例,用此实例来服务所有的请求。因此,你必须编写线程安全的Action类。遵循与写线程安全的servlet同样的方针。
但是,线程同步是不得以的方法,是比较复杂的,而且会带来性能的损失。等效的代码中,不需要同步在编写容易度和性能上会更好些。
我这里强调的是什么代码是始终为线程安全的、是不需要同步的。如下:
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类中只使用局部变量,不使用实例变量。


总结:
在Java的Web服务器环境下开发,要注意线程安全的问题。最简单的实现方式就是在Servlet和Struts Action里不要使用类变量、实例变量,但可以使用类常量和实例常量。
如果有这些变量,可以将它们转换为方法的参数传入,以消除它们。
注意一个容易混淆的地方:被Servlet或Action调用的类中(如值对象、领域模型类)中是否可以安全的使用实例变量?如果你在每次方法调用时
新建一个对象,再调用它们的方法,则不存在同步问题---因为它们不是多个线程共享的资源,只有共享的资源才需要同步---而Servlet和Action的实例对于多个线程是共享的。
换句话说,Servlet和Action的实例会被多个线程同时调用,而过了这一层,如果在你自己的代码中没有另外启动线程,且每次调用后续业务对象时都是先新建一个实例再调用,则都是线程安全的。


实例变量不正确的使用是造成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+"
");
}
}
}
  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系统提供的8个类变量:
JSP中用到的OUT,REQUEST,RESPONSE,SESSION,CONFIG,PAGE,PAGECONXT是线程安全的,APPLICATION在整个系统内被使用,所以不是线程安全的.
局部变量:   局部变量在堆栈中分配,因为每个线程都有它自己的堆栈空间,所以是线程安全的.
静态类:       静态类不用被实例化,就可直接使用,也不是线程安全的.
外部资源:   在程序中可能会有多个线程或进程同时操作同一个资源(如:多个线程或进程同时对一个文件进行写操作).

此时也要注意同步问题. 使它以单线程方式执行,这时,仍然只有一个实例,所有客户端的请求以串行方式执行。这样会降低系统的性能
对于存在线程不安全的类,如何避免出现线程安全问题:
1、采用synchronized同步。缺点就是存在堵塞问题。
2、使用ThreadLocal(实际上就是一个HashMap),这样不同的线程维护自己的对象,线程之间相互不干扰。




--------------------------------------------------------------------------------


ThreadLocal的设计
首先看看ThreadLocal的接口:
Object get() ; // 返回当前线程的线程局部变量副本 protected Object
initialValue(); // 返回该线程局部变量的当前线程的初始值                   
void set(Object value); // 设置当前线程的线程局部变量副本的值
  ThreadLocal有3个方法,其中值得注意的是initialValue(),该方法是一个protected
的方法,显然是为了子类重写而特意实现的。该方法返回当前线程在该线程局部变量的初始
值,这个方法是一个延迟调用方法,在一个线程第1次调用get()或者set(Object)时才执行
,并且仅执行1次。ThreadLocal中的确实实现直接返回一个null:
protected Object initialValue() { return null; }
  ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,
在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。比如下面的示例实现:

public class ThreadLocal
{
 private Map values = Collections.synchronizedMap(new HashMap());
 public Object get()
 {
  Thread curThread = Thread.currentThread();
  Object o = values.get(curThread);
  if (o == null && !values.containsKey(curThread))
  {
   o = initialValue();
   values.put(curThread, o);
  }
  return o;
 }

 public void set(Object newValue)
 {
  values.put(Thread.currentThread(), newValue);
 }

 public Object initialValue()
 {
  return null;
 }
}

  当然,这并不是一个工业强度的实现,但JDK中的ThreadLocal的实现总体思路也类似于此。
ThreadLocal的使用
  如果希望线程局部变量初始化其它值,那么需要自己实现ThreadLocal的子类并重写该
方法,通常使用一个内部匿名类对ThreadLocal进行子类化,比如下面的例子,SerialNum类
为每一个类分配一个序号:
public class SerialNum
{
 // The next serial number to be assigned
 private static int nextSerialNum = 0;
 private static ThreadLocal serialNum = new ThreadLocal()
 {
  protected synchronized Object initialValue()
  {
   return new Integer(nextSerialNum++);
  }
 };

 public static int get()
 {
  return ((Integer) (serialNum.get())).intValue();
 }
}

  SerialNum类的使用将非常地简单,因为get()方法是static的,所以在需要获取当前线
程的序号时,简单地调用:

int serial = SerialNum.get(); 即可。
  在线程是活动的并且ThreadLocal对象是可访问的时,该线程就持有一个到该线程局部
变量副本的隐含引用,当该线程运行结束后,该线程拥有的所以线程局部变量的副本都将失
效,并等待垃圾收集器收集。
ThreadLocal与其它同步机制的比较
  ThreadLocal和其它同步机制相比有什么优势呢?ThreadLocal和其它所有的同步机制都
是为了解决多线程中的对同一变量的访问冲突,在普通的同步机制中,是通过对象加锁来实
现多个线程对同一变量的安全访问的。这时该变量是多个线程共享的,使用这种同步机制需
要很细致地分析在什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放该
对象的锁等等很多。所有这些都是因为多个线程共享了资源造成的。ThreadLocal就从另一
个角度来解决多线程的并发访问,ThreadLocal会为每一个线程维护一个和该线程绑定的变
量的副本,从而隔离了多个线程的数据,每一个线程都拥有自己的变量副本,从而也就没有
必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时
,可以把不安全的整个变量封装进ThreadLocal,或者把该对象的特定于线程的状态封装进
ThreadLocal。
  由于ThreadLocal中可以持有任何类型的对象,所以使用ThreadLocal get当前线程的值
是需要进行强制类型转换。但随着新的Java版本(1.5)将模版的引入,新的支持模版参数
的ThreadLocal<T>类将从中受益。也可以减少强制类型转换,并将一些错误检查提前到了编
译期,将一定程度地简化ThreadLocal的使用。
总结
    当然ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了同
步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;而
ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量)
,这样当然不需要对多个线程进行同步了。所以,如果你需要进行多个线程之间进行通信,
则使用同步机制;如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极
大地简化你的程序,使程序更加易读、简洁。

ThreadLocal常见用途:
存放当前session用户
存放一些context变量,比如webwork的ActionContext
存放session,比如Spring hibernate orm的session


例子:用 ThreadLocal 实现每线程 Singleton
        线程局部变量常被用来描绘有状态“单子”(Singleton) 或线程安全的共享对象,或者是通过把不安全的整个变量封装进 ThreadLocal,或者是通过把对象的特定于线程的状态封装进 ThreadLocal。例如,在与数据库有紧密联系的应用程序中,程序的很多方法可能都需要访问数据库。在系统的每个方法中都包含一个 Connection 作为参数是不方便的 — 用“单子”来访问连接可能是一个虽然更粗糙,但却方便得多的技术。然而,多个线程不能安全地共享一个 JDBC Connection。如清单 3 所示,通过使用“单子”中的 ThreadLocal,我们就能让我们的程序中的任何类容易地获取每线程 Connection 的一个引用。这样,我们可以认为 ThreadLocal 允许我们创建每线程单子。
例:把一个 JDBC 连接存储到一个每线程 Singleton 中
public class ConnectionDispenser {
  private static class ThreadLocalConnection extends ThreadLocal {
           public Object initialValue() {
                  return DriverManager.getConnection(ConfigurationSingleton.getDbUrl());
           }
   }
    private ThreadLocalConnection conn = new ThreadLocalConnection();
    public static Connection getConnection() {
            return (Connection) conn.get();
   }
}

注意:
理论上来说,ThreadLocal是的确是相对于每个线程,每个线程会有自己的ThreadLocal。但是上面已经讲到,一般的应用服务器都会维护一套线程池。因此,不同用户访问,可能会接受到同样的线程。因此,在做基于TheadLocal时,需要谨慎,避免出现ThreadLocal变量的缓存,导致其他线程访问到本线程变量。



分享到:
评论

相关推荐

    servlet与Struts action线程安全问题分析

    Servlet和Struts Action是两种常见的Java Web开发组件,它们在多线程环境下运行时可能存在线程安全问题。线程安全是指在多线程环境中,一个类或者方法能够正确处理多个线程的并发访问,保证数据的一致性和完整性。 ...

    servlet与Struts action线程安全问题分析(pdf)

    Servlet作为一种常用的技术栈,因其多线程运行特性而具备较高的执行效率。然而,这种多线程特性同时也带来了一定程度上的线程安全问题。本文将深入探讨Servlet及其衍生框架Struts中的线程安全问题,并提出相应的解决...

    servlet和struts2笔记

    - 由于一个 Servlet 实例可能会同时处理多个请求,因此在 Servlet 中声明成员变量存储用户数据可能导致线程安全问题。 - **解决方案**: - 实现 `SingleThreadModel` 接口,但这种方式已被废弃,因为效率低下。 ...

    WEB多线程开发

    由于Servlet本质是多线程的,因此JSP天生支持多线程操作。在处理并发请求时,JSP和其背后的Servlet容器(如Tomcat)会利用多线程来提高性能。 2. **Servlet**:Servlet是Java编写的小型服务器端程序,用于扩展...

    servlet AND Struts2笔记

    - 在多线程环境下,如果Servlet中有成员变量来保存用户数据,则可能存在线程安全问题。 - 解决方案: - 实现`SingleThreadModel`接口(不推荐,因为这会影响Servlet的性能)。 - 使用局部变量保存用户数据(推荐...

    Java基础、Java集合、多线程、JDBC、HTTP、JSP、Servlet、Struts面试题汇总(附答案).docx

    在Java编程中,面试通常会涉及到多个核心概念,包括基础语法、集合框架、多线程、JDBC、HTTP协议以及Web开发中的JSP、Servlet和Struts等。以下是对这些知识点的详细说明: **Java基础** 1. `==`和`equals`的区别:`...

    struts2 与 struts1的区别

    ### Struts2 与 Struts1 的核心区别 #### 一、架构设计及Action实现方式 **Struts1** 在架构上要求Action类必须继承一个特定的抽象基类,这限制了开发者的灵活性,同时也意味着所有的Action都需要遵循相同的生命...

    Struts2与Struts1区别

    - Struts1 的 Action 直接依赖于 Servlet API,请求和响应对象会传入 execute 方法。而 Struts2 尽量减少了对容器的依赖,Action 可以独立于容器进行测试。虽然仍可以访问请求和响应对象,但多数情况下,Struts2 ...

    我的学习笔记集合(包括有corejava,jdbc,hibernate,ejb,c++,servlet,spring,struts,jsp)

    1. **Core Java**: Core Java是Java语言的基础,包括基本语法、数据类型、控制结构、类与对象、接口、异常处理、多线程、输入/输出流等内容。它是所有Java开发者必须掌握的知识,无论你是做桌面应用、服务器端开发...

    struts1与struts2本质区别

    这种模式虽然简化了内存管理,但增加了复杂度和潜在的错误风险,特别是在处理多线程环境时。 - **Struts2**:每个请求都会创建一个新的Action实例,消除了线程安全问题。这种模式使得Action设计更加简单直接,减少了...

    struts1与struts2的区别

    【Struts1与Struts2的区别】 Struts2框架并非完全独立的新框架,而是基于WebWork框架进行发展的。WebWork的稳定性和性能为Struts2提供了坚实的基础,使其成为一个融合了Struts1和WebWork优势的强大力量。在设计上,...

    转:struts1与struts2的区别

    ### Struts1与Struts2的主要区别 #### 1. Action类的设计差异 - **Struts1**: 在Struts1框架中,Action类必须继承自一个抽象类`org.apache.struts.action.Action`。这种方式可能导致的问题是代码灵活性较低,尤其...

    Struts线程安全

    同理,Struts框架中的Action类也会处于这种多线程环境中。 ### 多线程环境下的线程安全问题 #### 1. 什么是线程安全? 在多线程环境中能够正确执行且不会导致数据错误或程序故障的代码称为线程安全的代码。如果不...

    struts练习struts练习

    在Struts应用中,多线程问题可能出现在并发用户访问时,需要确保线程安全和资源管理。练习这部分可能涉及理解和解决并发问题,如同步控制、死锁和竞态条件。 3. **struts_user**: 这可能是处理用户相关操作的类或...

    Struts1与Struts2本质区别

    ### Struts1与Struts2本质区别 #### 1. 在Action实现类方面的对比 - **Struts 1**:要求Action类必须继承一个抽象基类`Action`,这种设计方式意味着开发者必须按照预设的结构来编写代码,灵活性较低。这种通过抽象...

    struts1和struts2的区别

    ### Struts1与Struts2的主要区别 #### 概述 Apache Struts 是一个用于构建企业级Java Web应用的开源框架。它分为两个版本:Struts1 和 Struts2。虽然两者都基于模型-视图-控制器(MVC)设计模式,但它们之间存在...

Global site tag (gtag.js) - Google Analytics