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日

相关文章

  • IDEA中的.iml文件和.idea文件夹

    下面我详细讲解一下“IDEA中的.iml文件和.idea文件夹”的完整攻略。 什么是.iml文件和.idea文件夹 在使用IntelliJ IDEA创建一个Java工程时,IDEA会自动生成 .iml 文件和 .idea 文件夹。.iml 文件是 IntelliJ IDEA 工程的描述文件,.idea 文件夹包含了整个工程的配置文件。 .iml文件的内容 .…

    Java 2023年5月19日
    00
  • 使用@JsonFormat和@DateTimeFormat对Date格式化操作

    使用@JsonFormat和@DateTimeFormat对Date格式化操作的完整攻略如下: @JsonFormat注解用于序列化Java对象,将日期格式化为指定的格式,例如将日期格式化为yyyy-MM-dd HH:mm:ss,其基本使用方式如下: @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",…

    Java 2023年5月26日
    00
  • java模拟hibernate一级缓存示例分享

    让我为您简单讲解一下如何使用Java模拟Hibernate一级缓存。 一、什么是Hibernate一级缓存 Hibernate是一个Java持久层框架,其缓存机制为应用程序和数据库之间搭建了一个缓冲层,用于提高性能并优化数据库资源的使用。Hibernate一级缓存,也称为session缓存,是Hibernate提供的默认缓存机制。当运行应用程序时,Hiber…

    Java 2023年5月20日
    00
  • SpringMVC配置404踩坑记录

    SpringMVC配置404踩坑记录 在使用SpringMVC开发Web应用程序时,我们经常会遇到404错误。本文将介绍如何在SpringMVC中配置404错误,并提供两个示例说明。 步骤一:配置web.xml 首先,我们需要在web.xml文件中配置SpringMVC的DispatcherServlet。可以通过添加以下配置来实现: <servlet…

    Java 2023年5月17日
    00
  • Java对象转换的方案分享

    下面就给大家详细讲解一下Java对象转换的方案分享,内容主要包括以下几个方面: 为什么需要Java对象转换 常见的Java对象转换方式和工具 示例说明:使用Jackson工具进行对象转换 示例说明:手动编写代码进行对象转换 1. 为什么需要Java对象转换 Java中的对象通常有很多种类型,比如字符串、数字、日期、自定义对象等等。在编程的过程中,我们可能需要…

    Java 2023年5月26日
    00
  • Java日常练习题,每天进步一点点(7)

    Java日常练习题系列是一组适合Java初学者的练习题,能够帮助Java新手提高编程水平和理解各种基础算法。对于第七篇“Java日常练习题,每天进步一点点(7)”,我会详细讲解攻略。 题目简介 本篇练习题共有5道题目,包含以下内容:1. 实现冒泡排序2. 编写水仙花数判断程序3. 编写斐波那契数列的程序4. 编写二分查找算法5. 编写插入排序 题目解析 1.…

    Java 2023年5月20日
    00
  • 基于Java内存溢出的解决方法详解

    基于Java内存溢出的解决方法详解 问题概述 Java程序常见的错误之一是内存溢出,也叫做Java堆溢出。这种问题出现的原因是因为Java应用程序耗尽了分配给应用程序的内存空间,导致应用程序不能继续工作。在实际生产环境中,经常会遇到Java应用程序因为内存溢出而崩溃,因此我们需要采取相应的措施解决这一问题。 解决方法详解 以下是一些常用的解决Java内存溢出…

    Java 2023年6月15日
    00
  • SpringBoot项目如何访问jsp页面的示例代码

    下面是关于Spring Boot项目访问jsp页面的攻略及两条示例说明。 一. 配置pom.xml文件 在Spring Boot项目的pom.xml文件中,添加如下依赖: <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>to…

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