Struts中action线程安全问题解析

yizhihongxing

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日

相关文章

  • 基于Spring中的线程池和定时任务功能解析

    我们来详细讲解一下“基于Spring中的线程池和定时任务功能解析”这个主题。 1. 线程池功能解析 1.1 Spring线程池概述 Spring提供了集成整个JDK标准线程池的功能,使我们可以方便地进行线程池的配置和使用。 在Spring中配置线程池,需要配置以下三个部分: TaskExecutor:Spring中的任务执行器,定义了多种任务和操作。 Thr…

    Java 2023年5月19日
    00
  • 详解Java如何实现数值校验的算法

    详解Java如何实现数值校验的算法 在Java中,数值校验是非常重要的一个操作。在开发过程中保证输入的数据的正确性非常关键,因此数值校验也是开发过程中经常需要用到的一项技术。下面我们将详细讲解如何实现数值校验的算法。 算法概述 数值校验的算法可以分为两类,分别是正则表达式和Java提供的API。 正则表达式实现 正则表达式是一种字符串匹配的技术,利用正则表达…

    Java 2023年5月19日
    00
  • python实现JAVA源代码从ANSI到UTF-8的批量转换方法

    下面是“python实现JAVA源代码从ANSI到UTF-8的批量转换方法”的完整攻略: 1. 安装Python 如果你的电脑上还没有Python,需要先安装Python。 请前往 https://www.python.org/downloads/ 下载并安装Python。 2. 编写Python代码 接下来需要编写Python代码来实现批量转换功能。具体代…

    Java 2023年5月20日
    00
  • SpringMVC集成Web与MVC执行流程和数据响应及交互相关介绍全面总结

    以下是关于“SpringMVC集成Web与MVC执行流程和数据响应及交互相关介绍全面总结”的完整攻略,其中包含两个示例。 SpringMVC集成Web与MVC执行流程和数据响应及交互相关介绍全面总结 SpringMVC是一个基于MVC模式的Web框架,它提供了一种灵活、高效的方式来开发Web应用程序。在SpringMVC中,Web和MVC是如何集成的?Spr…

    Java 2023年5月16日
    00
  • Spring JPA联表查询之OneToMany源码解析

    OK,这里是详细讲解“Spring JPA联表查询之OneToMany源码解析”的完整攻略。 一、背景介绍 在开发过程中,经常需要使用 JPA 进行数据库操作,其中,面对一对多关系的模型,我们可能需要使用到 JPA 的联表查询功能。本文将以一个简单的例子为基础,深入探究 Spring JPA 如何实现一对多关系的联表查询。 二、实例解析 考虑在一个商城系统中…

    Java 2023年5月20日
    00
  • Spring MVC-@RequestMapping注解详解

    下面就来详细讲解“Spring MVC-@RequestMapping注解详解”的完整攻略。 什么是Spring MVC @RequestMapping注解 @RequestMapping 是 Spring MVC 框架中最常用的注解之一,它可以用于方法上,用于指定 HTTP 请求的 URI,或者指定请求的方法 (GET、POST、PUT、DELETE 等)…

    Java 2023年5月16日
    00
  • Spring+SpringMVC+Hibernate项目环境搭建的步骤(图文)

    以下是关于“Spring+SpringMVC+Hibernate项目环境搭建的步骤(图文)”的完整攻略,其中包含两个示例。 Spring+SpringMVC+Hibernate项目环境搭建的步骤(图文) Spring+SpringMVC+Hibernate是一种常用的Java Web开发框架组合。在本文中,我们将讲解如何搭建一个Spring+SpringMV…

    Java 2023年5月17日
    00
  • 详解Java中Thread 和Runnable区别

    当开发多线程程序时,Java中有两种方式可以创建线程:继承Thread类或实现Runnable接口。虽然它们最终实现的目标是相同的,但它们之间仍然存在一些重要区别。本文将详细讲解Thread和Runnable的区别,让您在编写多线程程序时选择最佳方案。 一、继承Thread类 继承Thread类是创建线程的传统方式。这是通过继承Thread类并覆盖其中的ru…

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