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技术站