Struts中action线程安全问题解析

Struts中action线程安全问题解析

背景

Struts是Java Web应用程序的开发框架之一,它采用了MVC的设计模式,其中Action作为控制器部分的一部分,负责处理用户请求并返回响应。在使用Struts进行Web应用程序开发时,一个常见的问题是:是否需要考虑Action的线程安全性?当有多个用户同时发出请求时,是否会出现线程安全问题?

问题分析

通常情况下,一个Action类只会有一个实例,来负责处理多个请求。但是,在多线程情况下,如果不加以控制,可能会造成变量互相干扰,以及线程安全问题。主要表现为以下两种情况:

成员变量共享

如果在Action类中定义了一些成员变量,如下代码示例:

public class MyAction extends Action {
    private int count = 0;
    public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {
        count++;
        //some logic...
    }
}

在多线程情况下,如果有多个线程同时访问此Action实例,就有可能导致成员变量(如上述的count)造成共享的问题。举例来说,有两个请求同时访问MyAction实例,每个请求都对成员变量count进行自增操作,而两个操作可能并不是原子性的,就会出现计数器的混乱现象。

对象状态共享

在一个Action实例的执行过程中,可能会对共享对象的状态进行修改,如下代码示例:

public class MyAction extends Action {
    private MyService myService = new MyService();
    public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {
        myService.addCount();
        //some logic...
    }
}

在上述代码中,多个请求可能共享同一个MyAction实例,而每个请求又都会对相同的myService对象访问和修改,这就会导致对象状态共享的问题,或者说线程不安全的问题。

解决方案

在Struts框架中,为解决Action类的线程安全问题,需要考虑以下几个方面:

ActionScope

每个Action的请求映射(mapping)都有不同的作用域(scope),包括:request、session、application三种作用域。开发者可以根据实际情况,在struts-config.xml配置文件中为Action指定对应作用域。

例如:

<action path="/myAction" type="com.xxx.MyAction" scope="request">
    <forward name="success" path="/success.jsp" />
</action>

上述配置指定了MyAction的作用域为request,即每次请求结束后,Action对象都会被垃圾回收掉,避免了Action实例共享的问题。

ThreadLocal

ThreadLocal是一种Java的线程封闭技术,可以在同一线程内传递变量,它可以避免多线程并发修改变量引发的线程安全问题。在Struts中常使用ThreadLocal来解决Action中的线程安全问题。具体做法是,在Action类中定义每个变量的ThreadLocal,如下代码示例:

public class MyAction extends Action {
    private ThreadLocal<Integer> count = new ThreadLocal<Integer>() {
        protected Integer initialValue() {
            return 0;
        };
    };
    public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {
        count.set(count.get() + 1);
        //some logic...
    }
}

在上述代码中,每个线程都有一个独立的count变量,避免了线程安全问题。

单例模式

Struts中的Action默认是单例模式(Singleton)的,这就有可能导致线程安全问题。因此,在一些特定的场景下,开发者需要覆盖Action的默认行为,以每次请求都创建一个新的Action实例,如下代码示例:

public class MyAction extends Action {
    public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {
        //在此处新建一个Action实例
        MyAction action = new MyAction();
        //some logic...
    }
}

示例

举个栗子

在上述问题分析中,我们曾经提到了成员变量共享的问题。现在,我们来看一个示例。

public class CountAction extends Action {
    //计数器,在多线程请求下容易出问题
    private int count = 0;
    public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {
        count++;
        request.setAttribute("count", count);
        return mapping.findForward("index");
    }
}

在上述代码中,我们定义了一个CountAction,这个Action有一个成员变量:count。在每个请求执行的过程中,都会自增count的值,并把最新值放进request作用域中。接下来,我们来写个简单的测试:

@Test
public void testCountAction() throws Exception {
    CountAction action = new CountAction();
    MockHttpServletRequest request1 = new MockHttpServletRequest();
    MockHttpServletRequest request2 = new MockHttpServletRequest();
    MockHttpServletResponse response1 = new MockHttpServletResponse();
    MockHttpServletResponse response2 = new MockHttpServletResponse();
    action.execute(null, null, request1, response1);
    assertThat(request1.getAttribute("count"), equalTo(1));
    action.execute(null, null, request2, response2);
    assertThat(request2.getAttribute("count"), equalTo(2));
}

在上面的测试中,我们使用MockHttpServletRequest和MockHttpServletResponse构造了两个请求,分别访问CountAction。第一次访问后,期望request.getAttribute("count")的值为1;第二次访问后,期望request.getAttribute("count")的值为2。但是,这个测试会失败,因为count共享的问题。由于CountAction是单例模式,而我们测试用例中开启了两个线程分别访问该对象,导致count的值出现了不一致的情况。

线程安全的解决方案

为了解决上述问题,我们可以通过使用ThreadLocal绑定每个线程独立的计数器。修改后的代码对比如下:

public class SafeCountAction extends Action {
    //计数器,绑定到每个线程
    private ThreadLocal<Integer> count = new ThreadLocal<Integer>() {
        protected Integer initialValue() {
            return 0;
        };
    };
    public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {
        count.set(count.get() + 1);
        request.setAttribute("count", count.get());
        return mapping.findForward("index");
    }
}

我们可以在测试用例中等价地执行以下代码,并期望测试通过:

@Test
public void testSafeCountAction() throws Exception {
    SafeCountAction action = new SafeCountAction();
    MockHttpServletRequest request1 = new MockHttpServletRequest();
    MockHttpServletRequest request2 = new MockHttpServletRequest();
    MockHttpServletResponse response1 = new MockHttpServletResponse();
    MockHttpServletResponse response2 = new MockHttpServletResponse();
    action.execute(null, null, request1, response1);
    assertThat(request1.getAttribute("count"), equalTo(1));
    action.execute(null, null, request2, response2);
    assertThat(request2.getAttribute("count"), equalTo(1));
}

在上面的测试代码中,我们测试了两遍线程访问SafeCountAction对象,并将期望的值分别设为1,测试通过。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Struts中action线程安全问题解析 - Python技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • Request获取Session的方法总结

    Request获取Session的方法总结 Session是Web开发中常见的一种用户状态管理方式,可以在不同的页面之间传递和共享数据。在Python Web框架中,常用的Session实现方式是通过Request对象获取Session。以下是关于Request获取Session的方法总结。 通过Request的cookies属性获取Session Sess…

    Java 2023年6月15日
    00
  • Java如何获取JSON中某个对象的值

    获取JSON中某个对象的值最常用的方式是通过Java的JSON库将JSON字符串转换成Java中的对象,然后通过对象属性的方式获取需要的值。下面是获取JSON中某个对象的值的完整攻略以及两条示例说明: 步骤一:导入依赖 首先需要导入相关的依赖,本文使用的是Gson库,可以在项目中添加以下依赖: <dependency> <groupId&g…

    Java 2023年5月26日
    00
  • 使用mybatis-plus-generator进行代码自动生成的方法

    首先,我们需要了解一下mybatis-plus-generator的基本概念和用法。 mybatis-plus-generator是mybatis-plus框架中的一个代码自动生成工具,它能够根据数据库中的表结构自动生成实体类、Mapper接口、以及对应的XML文件等。使用mybatis-plus-generator可以大大提高我们的开发效率。 一、配置my…

    Java 2023年6月15日
    00
  • 详解在java中进行日期时间比较的4种方法

    关于在Java中进行日期时间比较的4种方法,这里为您详细讲解。 1. 使用Date类进行日期时间比较 Java中常用的日期时间比较方法之一就是使用Date类。Date类的compareTo方法可以比较两个日期的先后顺序。具体使用方法如下: Date date1 = new Date(); Date date2 = new Date(); if(date1.c…

    Java 2023年5月20日
    00
  • Java环境中MyBatis与Spring或Spring MVC框架的集成方法

    下面是关于“Java环境中MyBatis与Spring或Spring MVC框架的集成方法”的完整攻略,包含两个示例说明。 Java环境中MyBatis与Spring或Spring MVC框架的集成方法 在Java环境中,MyBatis与Spring或Spring MVC框架的集成非常常见。在本文中,我们将介绍如何将MyBatis与Spring或Spring…

    Java 2023年5月17日
    00
  • java 利用HttpClient PostMethod提交json数据操作

    下面是详细讲解Java利用HttpClient PostMethod提交JSON数据操作的完整攻略: 1. 导入HttpClient依赖 首先需要在项目中使用HttpClient库,可以使用Maven等方式导入依赖,例如: <dependency> <groupId>org.apache.httpcomponents</grou…

    Java 2023年5月26日
    00
  • LINQ to XML的编程基础

    LINQ to XML 是用于处理 XML 文档的 API,它允许我们通过 LINQ 查询语言来查询和对 XML 文档进行操作,相比传统 DOM 模型和 SAX 模型的 XML 处理方式,LINQ to XML 更具有灵活性和易用性。下面就是 LINQ to XML 的编程基础攻略: 1. 首先,需要引用相应的命名空间 使用 LINQ to XML,需要引用…

    Java 2023年5月19日
    00
  • 常用的java日期比较和日期计算方法小结

    当涉及处理日期和时间时,Java内置了许多日期类和方法来进行各种操作。在本文中,我们将探讨一些常用的日期比较和日期计算方法,这些方法可以帮助我们在Java中轻松处理各种日期和时间相关的操作。 比较日期 在Java中比较日期的最常用方法是使用compareTo方法。这个方法将返回一个整数,表示两个日期之间的差异。如果第一个日期在第二个日期之前,返回的整数将小于…

    Java 2023年5月20日
    00
合作推广
合作推广
分享本页
返回顶部