一、Servlet的线程安全问题
Servlet是单例的,如果Servlet有定义实例变量并且在service方法中有赋值操作,则在多线程情况下存在线程安全问题。
二、Struts1
Struts1使用的ActionServlet是单例的,由这一个servlet处理所有.do请求。RequestProcessor也是单例。
RequestProcessor的processActionCreate方法:
/**
* <p>Return an <code>Action</code> instance that will be used to process
* the current request, creating a new one if necessary.</p>
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
* @param mapping The mapping we are using
* @return An <code>Action</code> instance that will be used to process
* the current request.
* @throws IOException if an input/output error occurs
*/
protected Action processActionCreate(HttpServletRequest request,
HttpServletResponse response, ActionMapping mapping)
throws IOException {
// Acquire the Action instance we will be using (if there is one)
String className = mapping.getType();
if (log.isDebugEnabled()) {
log.debug(" Looking for Action instance for class " + className);
}
Action instance;
// 这个同步快保证了Action的单例
synchronized (actions) {
// Return any existing Action instance of this class
instance = (Action) actions.get(className);
if (instance != null) {
if (log.isTraceEnabled()) {
log.trace(" Returning existing Action instance");
}
return (instance);
}
// Create and return a new Action instance
if (log.isTraceEnabled()) {
log.trace(" Creating new Action instance");
}
try {
instance = (Action) RequestUtils.applicationInstance(className);
// Maybe we should propagate this exception
// instead of returning null.
} catch (Exception e) {
log.error(getInternal().getMessage("actionCreate",
mapping.getPath()), e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
getInternal().getMessage("actionCreate", mapping.getPath()));
return (null);
}
actions.put(className, instance);
if (instance.getServlet() == null) {
instance.setServlet(this.servlet);
}
}
return (instance);
}
可以看到struts1的action是单例的。线程安全问题同servlet,如果action中使用不当的实例变量,就有可能存在线程安全问题。
下面是摘自http://struts.apache.org/1.x/userGuide/building_controller.html 4.4.1 Action Class Design Guidelines 部分
Write code for a multi-threaded environment - Our controller servlet creates only one instance of your Action class , and uses this one instance to service all requests. Thus, you need to write thread-safe Action classes. Follow the same guidelines you would use to write thread-safe Servlets. Here are two general guidelines that will help you write scalable, thread-safe Action classes:
// 这里说的很清楚,ActionServlet只创建一个Action实例,所有对这个Action的请求都是由这个实例来处理。与写Servlet类类似。
* Only Use Local Variables - The most important principle that aids in thread-safe coding is to use only local variables , not instance variables , in your Action class. Local variables are created on a stack that is assigned (by your JVM) to each request thread, so there is no need to worry about sharing them. An Action can be factored into several local methods, so long as all variables needed are passed as method parameters. This assures thread safety, as the JVM handles such variables internally using the call stack which is associated with a single Thread.
// 在Action类中只使用局部变量,不使用实例变量。因为局部变量是放在每个线程的独立栈中的,不是共享资源不存在线程安全问题;另外传入方法的参数变量也是不存在线程安全问题。
* Conserve Resources - As a general rule, allocating scarce resources and keeping them across requests from the same user (in the user's session) can cause scalability problems. For example, if your application uses JDBC and you allocate a separate JDBC connection for every user, you are probably going to run in some scalability issues when your site suddenly shows up on Slashdot. You should strive to use pools and release resources (such as database connections) prior to forwarding control to the appropriate View component -- even if a bean method you have called throws an exception.
// 这里说的是-优化资源,使用池技术。一时想明白这跟线程安全有什么关系。
http://hi.baidu.com/niujunkai/blog/item/021964adc130660a4a36d6ab.html
总结什么代码是线程安全的、是不需要同步的。如下:
1)常量始终是线程安全的,因为只存在读操作。
2)对构造器的访问(new 操作)是线程安全的,因为每次都新建一个实例,不会访问共享的资源。
3)最重要的是:局部变量是线程安全的。因为每执行一个方法,都会在独立的空间创建局部变量,它不是共享的资源。局部变量包括方法的参数变量。
三、Struts2
Struts2为每个请求都实例化一个Action实例(Struts2+Spring框架中,spring的bean scope="singleton"以后另看),因此不存在多线程安全问题。
分析如下:
开始的FilterDispatcher和Dispatcher两个是单例,但在Dispatcher的serviceAction方法中创建的ActionProxy是局部变量,即只存在于各个线程的独立栈中,不是共享资源。
代码:
Configuration config = configurationManager.getConfiguration();
// 这里一开始从Ioc容器Container中取到的Factory也是单例的
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
// if the ActionMapping says to go straight to a result, do it!
if (mapping.getResult() != null) {
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
proxy.execute();
}
同样在ActionProxyFactory创建Proxy过程中生成的ActionInvocation也是局部变量,只与本线程相关。
ActionInvocation inv = new DefaultActionInvocation(extraContext, true);
container.inject(inv);
return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
创建实际Action的过程在ActionInvocation中:
action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
// 里边最终就是一个clazz.newInstance();
在上面过程中值得关注的另一个与多线程有关的处理是ActionContext类 。
该类中用到了ThreadLocal类来维持只与本次请求相关的参数。(ThreadLocal见ThreadLocal类 )
用ActionContext.getContext()即返回该ThreadLocal类,可以通过ctx.get(ActionContext.PARAMETERS),ctx.get(ActionContext.SESSION),ctx.get(ActionContext.APPLICATION)得到对应域的参数。
另外在看源码时发现DefaultActionInvocation的createResult()方法中用到了synchronized:
public Result createResult() throws Exception {
if (explicitResult != null) {
Result ret = explicitResult;
explicitResult = null;
return ret;
}
ActionConfig config = proxy.getConfig();
Map<String, ResultConfig> results = config.getResults();
ResultConfig resultConfig = null;
// 这里为什么需要同步?
synchronized (config) {
try {
resultConfig = results.get(resultCode);
} catch (NullPointerException e) {
// swallow
}
if (resultConfig == null) {
// If no result is found for the given resultCode, try to get a wildcard '*' match.
resultConfig = results.get("*");
}
}
if (resultConfig != null) {
try {
return objectFactory.buildResult(resultConfig, invocationContext.getContextMap());
} catch (Exception e) {
LOG.error("There was an exception while instantiating the result of type " + resultConfig.getClassName(), e);
throw new XWorkException(e, resultConfig);
}
} else if (resultCode != null && !Action.NONE.equals(resultCode) && unknownHandlerManager.hasUnknownHandlers()) {
return unknownHandlerManager.handleUnknownResult(invocationContext, proxy.getActionName(), proxy.getConfig(), resultCode);
}
return null;
}
虽然这里results有可能与其他线程共享而HashMap又不是线程安全,但这里只有get操作,没见到有存操作,为什么还需要同步?