将类从线程不安全迁移到线程安全

时间:2012-06-17 02:32:09

标签: java thread-safety struts

背景

Apache Action类不是线程安全的。但是,这只能在实现基类之后实现,系统中的所有其他类都依赖于该基类。基类使用许多实例变量:

  private HttpServletRequest request;
  private ArrayList inputParams = new ArrayList();
  private Connection connection;
  private String outputParameter;
  private boolean exportEnabled;

幸运的是,这些变量的所有用法都是通过访问器方法完成的。例如:

  public boolean getExportEnabled() {
    return this.exportEnabled;
  }

  public void setExportEnabled( boolean exportEnabled ) {
    this.exportEnabled = exportEnabled;
  }

问题

基类在多线程Servlet环境中运行。

解决方案#1

要解决此问题,我正在考虑使用键入会话的HashMap。但是,这需要重写所有方法和依赖代码:

  private static HashMap sessionVariables = new HashMap();

  public boolean getExportEnabled( HttpSession session ) {
    return getSessionVariables().get( session.getId() + '.exportEnabled' );
  }

  public void setExportEnabled( boolean exportEnabled, HttpSession session ) {
    getSessionVariables().put( session.getId() + '.exportEnabled', exportEnabled );
  }

这是很多工作,很可能会引入错误。

解决方案#2

可以将基类更改为“空”类。这个空类只有一个方法:

  public ActionForward execute(
    ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response )
    throws Exception {

    // Instantiate "this" and forward the request?
  }

但它必须知道适当的基类来实例化,或者可能实例化自身的新版本来处理调用。

更新#1

我相信Struts架构会做以下事情:

  1. 创建Action子类的实例。
  2. 为每个请求重复使用相同的实例。
  3. 接收新连接时获取线程(来自线程池)。
  4. 从线程中调用Action子类的execute
  5. 使用不同的线程处理多个新连接。
  6. 将在对象的同一实例上调用相同的execute方法,从而导致不安全的行为,因为子类具有实例变量。

    更新#2

    以下解决方案似乎解决了这个问题:

      public ActionForward execute(
              ActionMapping mapping,
              ActionForm form,
              HttpServletRequest request,
              HttpServletResponse response ) throws Exception {
          ((MyClass)clone()).executeClone( mapping, form, request, response );
      }
    
      public ActionForward executeClone(
              ActionMapping mapping,
              ActionForm form,
              HttpServletRequest request,
              HttpServletResponse response ) throws Exception {
    
          // Former "execute" method code goes here.
          // ...
      }
    

    原始execute方法已重命名为executeClone。新的execute实现创建了当前类的克隆,然后调用executeClone。这种微创技术避免了在使类线程安全的同时引入新的错误。

    问题

    在最小化引入错误的风险的同时,使代码线程安全的最可靠方法是什么?

    谢谢!

2 个答案:

答案 0 :(得分:1)

  

在最小化引入错误的风险的同时,使代码线程安全的最可靠方法是什么?

对此没有一般性的答案。使类线程安全所需要做的事情取决于类的功能,API设计以及所需的线程安全级别。在某些情况下,制作线程安全的东西是不切实际的;例如阅读Collections.synchonizedList的javadoc,看看它如何处理iterator()方法。

答案 1 :(得分:1)

解决方案#1很危险,因为它假定会话是线程安全的,但不一定是这种情况。有人可能会在同一个会话中同时发出两个请求。

通过使基类实现Cloneable,可以轻松实现解决方案#2。然后它可以克隆自己,设置克隆的实例变量,然后调用super.execute()。如果你认为改变设计太难以使你的基类正确地是线程安全的,那么这可能是一个简单的出路。